Add I2C hot-plug detection and streaming diagnostics (firmware v3.04.0)

Phase D firmware hardening: vendor commands 0xBD (streaming diagnostics)
and 0xBE (I2C hot-plug detection) with Python library, bridge, and demo
support. All I2C operations use timeout-protected helpers, BCM4500 reads
are rate-limited during streaming, and frame counter reads use atomic
read-verify-reread pattern. Counters saturate instead of wrapping.
This commit is contained in:
Ryan Malloy 2026-02-15 18:24:45 -07:00
parent 592898dd7a
commit 6e353c351f
4 changed files with 372 additions and 4 deletions

View file

@ -63,6 +63,8 @@ CMD_MULTI_REG_READ = 0xB9
CMD_PARAM_SWEEP = 0xBA
CMD_ADAPTIVE_BLIND_SCAN = 0xBB
CMD_GET_LAST_ERROR = 0xBC
CMD_GET_STREAM_DIAG = 0xBD
CMD_GET_HOTPLUG_STATUS = 0xBE
# Error codes (returned by CMD_GET_LAST_ERROR)
ERR_OK = 0x00
@ -789,6 +791,68 @@ class SkyWalker1:
"""Disable east/west soft limits."""
self.send_diseqc_message(diseqc_disable_limits())
# -- Streaming diagnostics (v3.04+) --
def get_stream_diag(self, reset: bool = False) -> dict:
"""Read streaming diagnostics counters (0xBD).
Returns dict with poll_count, overflow_count, sync_loss,
last_status, last_lock, armed, had_sync.
Set reset=True to clear counters after read.
"""
wval = 1 if reset else 0
data = self._vendor_in(CMD_GET_STREAM_DIAG, value=wval, length=12)
poll_count = struct.unpack_from('<I', data, 0)[0]
overflow_count = struct.unpack_from('<H', data, 4)[0]
sync_loss = struct.unpack_from('<H', data, 6)[0]
return {
"poll_count": poll_count,
"overflow_count": overflow_count,
"sync_loss": sync_loss,
"last_status": data[8],
"last_lock": data[9],
"armed": bool(data[10]),
"had_sync": bool(data[11]),
}
# -- I2C hot-plug detection (v3.04+) --
def get_hotplug_status(self, reset: bool = False,
force_scan: bool = False) -> dict:
"""Read I2C hot-plug detection status (0xBE).
Returns dict with current/previous bus bitmaps, change count,
devices added/removed in last scan, and decoded address lists.
Set reset=True to clear change counter.
Set force_scan=True to trigger immediate I2C rescan.
"""
wval = 2 if force_scan else (1 if reset else 0)
data = self._vendor_in(CMD_GET_HOTPLUG_STATUS, value=wval, length=36)
current_bitmap = bytes(data[0:16])
changes = struct.unpack_from('<H', data, 16)[0]
added = data[18]
removed = data[19]
previous_bitmap = bytes(data[20:36])
return {
"current_bitmap": current_bitmap,
"previous_bitmap": previous_bitmap,
"changes": changes,
"added": added,
"removed": removed,
"current_devices": _bitmap_to_addrs(current_bitmap),
"previous_devices": _bitmap_to_addrs(previous_bitmap),
}
def _bitmap_to_addrs(bitmap: bytes) -> list[int]:
"""Convert 16-byte I2C address bitmap to list of 7-bit addresses."""
addrs = []
for byte_idx in range(16):
for bit in range(8):
if bitmap[byte_idx] & (1 << bit):
addrs.append((byte_idx << 3) | bit)
return addrs
# --- DiSEqC 1.2 command builders ---