Files
Button/collector/protocol.py
T
2026-11-02 01:23:50 +03:00

82 lines
2.0 KiB
Python

#!/usr/bin/env python3
"""Pack/unpack 4-byte LE sensor reports and slot/config replies."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Tuple
BATT_UNKNOWN = 0xFF
DIST_NO_TARGET = 0xFFFF
SLOT_UNASSIGNED = 0xFF
FLAG_LOW_BATT = 0x01
FLAG_FAULT = 0x02
FLAG_NO_TARGET = 0x04
FLAG_UNCHANGED = 0x08
CFG_JOIN_OPEN = 0x01
CFG_POOL_FULL = 0x02
CFG_BUSY = 0x04
@dataclass(frozen=True)
class SensorReport:
battery: int
flags: int
dist: int
@dataclass(frozen=True)
class SlotConfig:
slot: int
flags: int
period_s: int
def unpack_report(data: bytes) -> Optional[SensorReport]:
if data is None or len(data) != 4:
return None
battery = data[0]
flags = data[1]
dist = data[2] | (data[3] << 8)
return SensorReport(battery=battery, flags=flags, dist=dist)
def pack_report(battery: int, flags: int, dist: int) -> bytes:
batt = max(0, min(255, int(battery))) & 0xFF
fl = int(flags) & 0xFF
d = max(0, min(0xFFFF, int(dist))) & 0xFFFF
return bytes((batt, fl, d & 0xFF, (d >> 8) & 0xFF))
def unpack_slot_config(data: bytes) -> Optional[SlotConfig]:
if data is None or len(data) != 4:
return None
slot = data[0]
flags = data[1]
period = data[2] | (data[3] << 8)
return SlotConfig(slot=slot, flags=flags, period_s=period)
def pack_slot_config(slot: int, flags: int, period_s: int) -> bytes:
s = max(0, min(255, int(slot))) & 0xFF
fl = int(flags) & 0xFF
p = max(0, min(0xFFFF, int(period_s))) & 0xFFFF
return bytes((s, fl, p & 0xFF, (p >> 8) & 0xFF))
def report_to_bytes(r: SensorReport) -> bytes:
return pack_report(r.battery, r.flags, r.dist)
def pack_interval_min(minutes: int) -> bytes:
"""2-byte LE uint16 report interval in minutes."""
m = max(1, min(0xFFFF, int(minutes))) & 0xFFFF
return bytes((m & 0xFF, (m >> 8) & 0xFF))
def unpack_interval_min(data: bytes) -> Optional[int]:
if data is None or len(data) != 2:
return None
return data[0] | (data[1] << 8)