初始化奇妙小屏幕控制器项目
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
from decimal import Decimal, ROUND_FLOOR
|
||||
|
||||
LOW_VOLTAGE_CRITICAL_THRESHOLD = 4.5
|
||||
LOW_VOLTAGE_LIMIT_THRESHOLD = 4.8
|
||||
LOW_VOLTAGE_CRITICAL_BRIGHTNESS_PERCENT = 35
|
||||
|
||||
PROTECTION_MODE_DISABLED = "disabled"
|
||||
PROTECTION_MODE_UNAVAILABLE = "unavailable"
|
||||
PROTECTION_MODE_INACTIVE = "inactive"
|
||||
PROTECTION_MODE_LIMITING = "limiting"
|
||||
PROTECTION_MODE_CRITICAL = "critical"
|
||||
|
||||
NORMAL_SAMPLING_INTERVAL_SECONDS = 5.0
|
||||
LIMITING_SAMPLING_INTERVAL_SECONDS = 1.0
|
||||
FIRST_CRITICAL_SAMPLING_INTERVAL_SECONDS = 0.5
|
||||
MIN_CRITICAL_SAMPLING_INTERVAL_SECONDS = 0.5
|
||||
MAX_CRITICAL_SAMPLING_INTERVAL_SECONDS = 2.0
|
||||
MAX_CRITICAL_SLOWDOWN_STEP_SECONDS = 0.5
|
||||
ERROR_RETRY_INTERVALS_SECONDS = (1.0, 2.0, 5.0)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LowVoltageProtectionDirective:
|
||||
sequence: int
|
||||
enabled: bool
|
||||
mode: str
|
||||
brightness_limit_percent: int | None
|
||||
reading_stale: bool
|
||||
|
||||
|
||||
def brightness_limit_for_voltage(volts: float) -> int | None:
|
||||
"""Return the output cap for a calibrated, unrounded voltage reading."""
|
||||
voltage = float(volts)
|
||||
if not math.isfinite(voltage):
|
||||
raise ValueError("voltage must be finite")
|
||||
if voltage > LOW_VOLTAGE_LIMIT_THRESHOLD:
|
||||
return None
|
||||
if voltage < LOW_VOLTAGE_CRITICAL_THRESHOLD:
|
||||
return LOW_VOLTAGE_CRITICAL_BRIGHTNESS_PERCENT
|
||||
if voltage == LOW_VOLTAGE_LIMIT_THRESHOLD:
|
||||
return 100
|
||||
if voltage == LOW_VOLTAGE_CRITICAL_THRESHOLD:
|
||||
return 50
|
||||
decimal_voltage = Decimal(str(voltage))
|
||||
limit = int(
|
||||
(
|
||||
Decimal(50)
|
||||
+ Decimal(50)
|
||||
* (decimal_voltage - Decimal("4.5"))
|
||||
/ Decimal("0.3")
|
||||
).to_integral_value(rounding=ROUND_FLOOR)
|
||||
)
|
||||
return max(50, min(100, limit))
|
||||
|
||||
|
||||
def protection_directive_for_voltage(
|
||||
sequence: int,
|
||||
enabled: bool,
|
||||
volts: float | None,
|
||||
*,
|
||||
reading_stale: bool = False,
|
||||
) -> LowVoltageProtectionDirective:
|
||||
if not enabled:
|
||||
return LowVoltageProtectionDirective(
|
||||
sequence=sequence,
|
||||
enabled=False,
|
||||
mode=PROTECTION_MODE_DISABLED,
|
||||
brightness_limit_percent=None,
|
||||
reading_stale=False,
|
||||
)
|
||||
if volts is None:
|
||||
return LowVoltageProtectionDirective(
|
||||
sequence=sequence,
|
||||
enabled=True,
|
||||
mode=PROTECTION_MODE_UNAVAILABLE,
|
||||
brightness_limit_percent=None,
|
||||
reading_stale=reading_stale,
|
||||
)
|
||||
|
||||
limit = brightness_limit_for_voltage(volts)
|
||||
if volts > LOW_VOLTAGE_LIMIT_THRESHOLD:
|
||||
mode = PROTECTION_MODE_INACTIVE
|
||||
limit = None
|
||||
elif volts < LOW_VOLTAGE_CRITICAL_THRESHOLD:
|
||||
mode = PROTECTION_MODE_CRITICAL
|
||||
else:
|
||||
mode = PROTECTION_MODE_LIMITING
|
||||
return LowVoltageProtectionDirective(
|
||||
sequence=sequence,
|
||||
enabled=True,
|
||||
mode=mode,
|
||||
brightness_limit_percent=limit,
|
||||
reading_stale=reading_stale,
|
||||
)
|
||||
|
||||
|
||||
def same_protection_level(
|
||||
left: LowVoltageProtectionDirective,
|
||||
right: LowVoltageProtectionDirective,
|
||||
) -> bool:
|
||||
return (
|
||||
left.enabled == right.enabled
|
||||
and left.mode == right.mode
|
||||
and left.brightness_limit_percent == right.brightness_limit_percent
|
||||
)
|
||||
|
||||
|
||||
def is_more_restrictive(
|
||||
candidate: LowVoltageProtectionDirective,
|
||||
current: LowVoltageProtectionDirective,
|
||||
) -> bool:
|
||||
ranks = {
|
||||
PROTECTION_MODE_DISABLED: 0,
|
||||
PROTECTION_MODE_UNAVAILABLE: 0,
|
||||
PROTECTION_MODE_INACTIVE: 0,
|
||||
PROTECTION_MODE_LIMITING: 1,
|
||||
PROTECTION_MODE_CRITICAL: 2,
|
||||
}
|
||||
candidate_rank = ranks[candidate.mode]
|
||||
current_rank = ranks[current.mode]
|
||||
if candidate_rank != current_rank:
|
||||
return candidate_rank > current_rank
|
||||
if candidate.mode == PROTECTION_MODE_LIMITING:
|
||||
assert candidate.brightness_limit_percent is not None
|
||||
assert current.brightness_limit_percent is not None
|
||||
return candidate.brightness_limit_percent < current.brightness_limit_percent
|
||||
return False
|
||||
|
||||
|
||||
def more_conservative(
|
||||
left: LowVoltageProtectionDirective,
|
||||
right: LowVoltageProtectionDirective,
|
||||
) -> LowVoltageProtectionDirective:
|
||||
"""Choose the stricter directive while retaining the newest sequence on a tie."""
|
||||
if is_more_restrictive(left, right):
|
||||
return left
|
||||
if is_more_restrictive(right, left):
|
||||
return right
|
||||
return right if right.sequence >= left.sequence else left
|
||||
|
||||
|
||||
def error_retry_interval(consecutive_errors: int) -> float:
|
||||
if consecutive_errors <= 0:
|
||||
return NORMAL_SAMPLING_INTERVAL_SECONDS
|
||||
index = min(consecutive_errors, len(ERROR_RETRY_INTERVALS_SECONDS)) - 1
|
||||
return ERROR_RETRY_INTERVALS_SECONDS[index]
|
||||
|
||||
|
||||
def successful_sampling_interval(
|
||||
*,
|
||||
enabled: bool,
|
||||
mode: str,
|
||||
volts: float,
|
||||
previous_volts: float | None,
|
||||
elapsed_seconds: float | None,
|
||||
previous_interval_seconds: float,
|
||||
first_critical_sample: bool,
|
||||
) -> float:
|
||||
"""Return the next group-start interval for a successful voltage sample."""
|
||||
if not enabled:
|
||||
return NORMAL_SAMPLING_INTERVAL_SECONDS
|
||||
if mode == PROTECTION_MODE_LIMITING:
|
||||
return LIMITING_SAMPLING_INTERVAL_SECONDS
|
||||
if mode == PROTECTION_MODE_CRITICAL:
|
||||
if first_critical_sample:
|
||||
return FIRST_CRITICAL_SAMPLING_INTERVAL_SECONDS
|
||||
rate = _voltage_rate(volts, previous_volts, elapsed_seconds)
|
||||
target = _clamp(
|
||||
MIN_CRITICAL_SAMPLING_INTERVAL_SECONDS,
|
||||
MAX_CRITICAL_SAMPLING_INTERVAL_SECONDS,
|
||||
0.02 / max(abs(rate), 0.01),
|
||||
)
|
||||
if target > previous_interval_seconds:
|
||||
return min(
|
||||
target,
|
||||
previous_interval_seconds + MAX_CRITICAL_SLOWDOWN_STEP_SECONDS,
|
||||
)
|
||||
return target
|
||||
if mode == PROTECTION_MODE_INACTIVE:
|
||||
rate = _voltage_rate(volts, previous_volts, elapsed_seconds)
|
||||
if rate < 0:
|
||||
seconds_to_limit = (volts - LOW_VOLTAGE_LIMIT_THRESHOLD) / -rate
|
||||
return _clamp(1.0, NORMAL_SAMPLING_INTERVAL_SECONDS, seconds_to_limit)
|
||||
return NORMAL_SAMPLING_INTERVAL_SECONDS
|
||||
|
||||
|
||||
def _voltage_rate(
|
||||
volts: float,
|
||||
previous_volts: float | None,
|
||||
elapsed_seconds: float | None,
|
||||
) -> float:
|
||||
if (
|
||||
previous_volts is None
|
||||
or elapsed_seconds is None
|
||||
or not math.isfinite(elapsed_seconds)
|
||||
or elapsed_seconds <= 0
|
||||
):
|
||||
return 0.0
|
||||
return (volts - previous_volts) / elapsed_seconds
|
||||
|
||||
|
||||
def _clamp(minimum: float, maximum: float, value: float) -> float:
|
||||
return max(minimum, min(maximum, value))
|
||||
Reference in New Issue
Block a user