129 lines
3.8 KiB
Python
129 lines
3.8 KiB
Python
"""Public domain models used by the controller and GUI layers."""
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
from enum import IntFlag
|
|
from typing import Any
|
|
|
|
from .constants import (
|
|
VOLT_3V3_MAX,
|
|
VOLT_3V3_MIN,
|
|
VOLT_5V_MAX,
|
|
VOLT_5V_MIN,
|
|
VOLT_7V_MAX,
|
|
VOLT_7V_MIN,
|
|
)
|
|
|
|
class DeviceState(IntFlag):
|
|
"""Bit-mask of device error flags returned by the firmware status packet."""
|
|
|
|
OK = 0x0000
|
|
SD_ERROR = 0x0001
|
|
UART_ERROR = 0x0002
|
|
UART_DECODE_ERROR = 0x0004
|
|
TEC1_ERROR = 0x0008
|
|
TEC2_ERROR = 0x0010
|
|
DEFAULT_ERROR = 0x0020
|
|
AD9102_ERROR = 0x0080
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class Measurements:
|
|
"""Latest live telemetry frame decoded from the board."""
|
|
|
|
current1: float
|
|
current2: float
|
|
temp1: float
|
|
temp2: float
|
|
temp_ext1: float | None = None
|
|
temp_ext2: float | None = None
|
|
voltage_3v3: float = 0.0
|
|
voltage_5v1: float = 0.0
|
|
voltage_5v2: float = 0.0
|
|
voltage_7v0: float = 0.0
|
|
message_id: int | None = None
|
|
to6_counter_lsb: int | None = None
|
|
to6_counter_msb: int | None = None
|
|
timestamp: datetime = field(default_factory=datetime.now)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Return a JSON-friendly representation."""
|
|
return {
|
|
"current1": self.current1,
|
|
"current2": self.current2,
|
|
"temp1": self.temp1,
|
|
"temp2": self.temp2,
|
|
"temp_ext1": self.temp_ext1,
|
|
"temp_ext2": self.temp_ext2,
|
|
"voltage_3v3": self.voltage_3v3,
|
|
"voltage_5v1": self.voltage_5v1,
|
|
"voltage_5v2": self.voltage_5v2,
|
|
"voltage_7v0": self.voltage_7v0,
|
|
"message_id": self.message_id,
|
|
"to6_counter_lsb": self.to6_counter_lsb,
|
|
"to6_counter_msb": self.to6_counter_msb,
|
|
"timestamp": self.timestamp.isoformat(),
|
|
}
|
|
|
|
def check_power_rails(self) -> dict[str, bool]:
|
|
"""Check nominal supply rails against static tolerances."""
|
|
return {
|
|
"3v3": VOLT_3V3_MIN <= self.voltage_3v3 <= VOLT_3V3_MAX,
|
|
"5v1": VOLT_5V_MIN <= self.voltage_5v1 <= VOLT_5V_MAX,
|
|
"5v2": VOLT_5V_MIN <= self.voltage_5v2 <= VOLT_5V_MAX,
|
|
"7v0": VOLT_7V_MIN <= self.voltage_7v0 <= VOLT_7V_MAX,
|
|
}
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class DeviceStatus:
|
|
"""Decoded two-byte status response from the board."""
|
|
|
|
state: DeviceState = DeviceState.OK
|
|
detail: int = 0
|
|
measurements: Measurements | None = None
|
|
is_connected: bool = False
|
|
last_command_id: int | None = None
|
|
error_message: str | None = None
|
|
|
|
@property
|
|
def has_error(self) -> bool:
|
|
"""Return True when any firmware error bit is set."""
|
|
return self.state != DeviceState.OK
|
|
|
|
@property
|
|
def is_ok(self) -> bool:
|
|
"""Convenience alias for the common no-error case."""
|
|
return not self.has_error
|
|
|
|
@property
|
|
def active_errors(self) -> list[str]:
|
|
"""Return the names of all active error flags."""
|
|
return [
|
|
flag.name
|
|
for flag in DeviceState
|
|
if flag is not DeviceState.OK and (self.state & flag) == flag
|
|
]
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
"""Return a JSON-friendly representation."""
|
|
return {
|
|
"state_mask": int(self.state),
|
|
"state_names": self.active_errors,
|
|
"detail": self.detail,
|
|
"measurements": self.measurements.to_dict() if self.measurements else None,
|
|
"is_connected": self.is_connected,
|
|
"last_command_id": self.last_command_id,
|
|
"error_message": self.error_message,
|
|
"has_error": self.has_error,
|
|
}
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class ProfileSaveRequest:
|
|
"""Rendered profile payload that should be persisted on the device SD card."""
|
|
|
|
profile_name: str
|
|
profile_text: str
|
|
waveform_text: str = ""
|