Coverage for kv4p/messages/device_state.py: 100%
39 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 13:27 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-13 13:27 +0000
1"""Firmware-applied device state."""
3from __future__ import annotations
5import struct
6from dataclasses import dataclass
7from enum import IntEnum
9from kv4p.constants.messages import DEVICE_STATE_SQUELCHED
11_DEVICE_STATE = struct.Struct("<IiHBffBBBcBBB")
12DEVICE_STATE_SIZE = _DEVICE_STATE.size
15class RadioMode(IntEnum):
16 """Firmware radio mode, as reported in ``DeviceState.mode``."""
18 TX = 0
19 RX = 1
20 STOPPED = 2
21 UNKNOWN = -1
23 @classmethod
24 def _missing_(cls, value: object) -> RadioMode:
25 return cls.UNKNOWN
28@dataclass(frozen=True, slots=True)
29class DeviceState:
30 """Firmware-applied state."""
32 applied_sequence: int
33 memory_id: int
34 flags: int
35 bw: int
36 freq_tx: float
37 freq_rx: float
38 ctcss_tx: int
39 squelch: int
40 ctcss_rx: int
41 radio_module_status: str
42 mode: RadioMode
43 last_error: int
44 latest_rssi: int
46 @classmethod
47 def from_bytes(cls, payload: bytes) -> DeviceState:
48 """Parse DeviceState."""
49 if len(payload) < _DEVICE_STATE.size:
50 raise ValueError(f"DeviceState payload too short: {len(payload)}")
51 values = _DEVICE_STATE.unpack(payload[: _DEVICE_STATE.size])
52 return cls(
53 applied_sequence=values[0],
54 memory_id=values[1],
55 flags=values[2],
56 bw=values[3],
57 freq_tx=values[4],
58 freq_rx=values[5],
59 ctcss_tx=values[6],
60 squelch=values[7],
61 ctcss_rx=values[8],
62 radio_module_status=values[9].decode("ascii", errors="replace"),
63 mode=RadioMode(values[10]),
64 last_error=values[11],
65 latest_rssi=values[12],
66 )
68 @property
69 def sql_open(self) -> bool:
70 """Return true when the device squelch is open."""
71 return not bool(self.flags & DEVICE_STATE_SQUELCHED)