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

@ -242,3 +242,13 @@ class USBBridge:
def get_last_error_str(self) -> str:
with self._lock:
return self._dev.get_last_error_str()
def get_stream_diag(self, reset: bool = False) -> dict:
with self._lock:
return self._dev.get_stream_diag(reset=reset)
def get_hotplug_status(self, reset: bool = False,
force_scan: bool = False) -> dict:
with self._lock:
return self._dev.get_hotplug_status(reset=reset,
force_scan=force_scan)

View file

@ -576,6 +576,51 @@ class DemoDevice:
rng = random.Random(start_reg)
return bytes(rng.randint(0, 255) for _ in range(count))
def get_stream_diag(self, reset: bool = False) -> dict:
"""Simulated streaming diagnostics."""
self._sd_poll_count = getattr(self, '_sd_poll_count', 0) + random.randint(50, 200)
self._sd_overflow_count = getattr(self, '_sd_overflow_count', 0)
if random.random() < 0.02: # 2% chance of overflow per call
self._sd_overflow_count += 1
sig = self.signal_monitor()
is_locked = sig["locked"]
result = {
"poll_count": self._sd_poll_count,
"overflow_count": self._sd_overflow_count,
"sync_loss": getattr(self, '_sd_sync_loss', 0),
"last_status": 0x42,
"last_lock": 0x20 if is_locked else 0x00,
"armed": True,
"had_sync": is_locked,
}
if reset:
self._sd_poll_count = 0
self._sd_overflow_count = 0
self._sd_sync_loss = 0
return result
def get_hotplug_status(self, reset: bool = False,
force_scan: bool = False) -> dict:
"""Simulated I2C hot-plug detection."""
# Standard SkyWalker-1 I2C devices: BCM4500 (0x08), EEPROM (0x50),
# tuner (0x61), LNB controller (0x08 shares)
bitmap = bytearray(16)
for addr in [0x08, 0x50, 0x51, 0x61]:
bitmap[addr >> 3] |= (1 << (addr & 0x07))
current = bytes(bitmap)
previous = current # no changes in demo
addrs = [0x08, 0x50, 0x51, 0x61]
result = {
"current_bitmap": current,
"previous_bitmap": previous,
"changes": 0,
"added": 0,
"removed": 0,
"current_devices": addrs,
"previous_devices": addrs,
}
return result
# --- Internal signal model ---
def _power_at(self, freq_mhz: float, elapsed: float) -> float: