Coverage for kv4p/messages/version.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-13 13:27 +0000

1"""Firmware version payload (part of HELLO).""" 

2 

3from __future__ import annotations 

4 

5import struct 

6from dataclasses import dataclass 

7from enum import IntFlag 

8 

9_VERSION = struct.Struct("<HcIBffB") 

10 

11 

12class RadioFeatures(IntFlag): 

13 """Firmware capability flags, as reported in ``Version.features``.""" 

14 

15 HAS_HL = 1 << 0 

16 HAS_PHY_PTT = 1 << 1 

17 HAS_ESP32_AFSK = 1 << 2 

18 

19 @property 

20 def hl(self) -> bool: 

21 """True when the firmware reports HL support.""" 

22 return RadioFeatures.HAS_HL in self 

23 

24 @property 

25 def phy_ptt(self) -> bool: 

26 """True when the firmware reports a physical PTT input.""" 

27 return RadioFeatures.HAS_PHY_PTT in self 

28 

29 @property 

30 def esp32_afsk(self) -> bool: 

31 """True when the firmware reports ESP32-side AFSK support.""" 

32 return RadioFeatures.HAS_ESP32_AFSK in self 

33 

34 

35@dataclass(frozen=True, slots=True) 

36class Version: 

37 """Firmware version payload.""" 

38 

39 ver: int 

40 radio_module_status: str 

41 window_size: int 

42 rf_module_type: int 

43 min_radio_freq: float 

44 max_radio_freq: float 

45 features: RadioFeatures 

46 

47 @classmethod 

48 def from_bytes(cls, payload: bytes) -> Version: 

49 """Parse Version.""" 

50 if len(payload) != _VERSION.size: 

51 raise ValueError(f"Version payload must be {_VERSION.size} bytes, got {len(payload)}") 

52 values = _VERSION.unpack(payload) 

53 return cls( 

54 ver=values[0], 

55 radio_module_status=values[1].decode("ascii", errors="replace"), 

56 window_size=values[2], 

57 rf_module_type=values[3], 

58 min_radio_freq=values[4], 

59 max_radio_freq=values[5], 

60 features=RadioFeatures(values[6]), 

61 )