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:
parent
592898dd7a
commit
6e353c351f
@ -5,8 +5,9 @@
|
|||||||
* Stock-compatible vendor commands (0x80-0x94) plus custom
|
* Stock-compatible vendor commands (0x80-0x94) plus custom
|
||||||
* spectrum sweep, raw demod access, blind scan (0xB0-0xB3),
|
* spectrum sweep, raw demod access, blind scan (0xB0-0xB3),
|
||||||
* hardware diagnostics (0xB4-0xB6), signal monitoring (0xB7-0xB9),
|
* hardware diagnostics (0xB4-0xB6), signal monitoring (0xB7-0xB9),
|
||||||
* and advanced commands: parameterized sweep (0xBA), adaptive
|
* advanced commands: parameterized sweep (0xBA), adaptive blind scan
|
||||||
* blind scan (0xBB), error codes (0xBC), DiSEqC messaging (0x8D).
|
* (0xBB), error codes (0xBC), DiSEqC messaging (0x8D), streaming
|
||||||
|
* diagnostics (0xBD), and I2C hot-plug detection (0xBE).
|
||||||
*
|
*
|
||||||
* SDCC + fx2lib toolchain. Loaded into FX2 RAM for testing.
|
* SDCC + fx2lib toolchain. Loaded into FX2 RAM for testing.
|
||||||
*/
|
*/
|
||||||
@ -62,6 +63,8 @@
|
|||||||
#define PARAM_SWEEP 0xBA
|
#define PARAM_SWEEP 0xBA
|
||||||
#define ADAPTIVE_BLIND_SCAN 0xBB
|
#define ADAPTIVE_BLIND_SCAN 0xBB
|
||||||
#define GET_LAST_ERROR 0xBC
|
#define GET_LAST_ERROR 0xBC
|
||||||
|
#define GET_STREAM_DIAG 0xBD
|
||||||
|
#define GET_HOTPLUG_STATUS 0xBE
|
||||||
|
|
||||||
/* error codes (set by I2C helpers, read via 0xBC) */
|
/* error codes (set by I2C helpers, read via 0xBC) */
|
||||||
#define ERR_OK 0x00
|
#define ERR_OK 0x00
|
||||||
@ -114,6 +117,25 @@ static __xdata BYTE last_error;
|
|||||||
/* Shared scratch buffer for vendor command case blocks (saves DSEG) */
|
/* Shared scratch buffer for vendor command case blocks (saves DSEG) */
|
||||||
static __xdata BYTE vc_diag[8];
|
static __xdata BYTE vc_diag[8];
|
||||||
|
|
||||||
|
/* I2C hot-plug detection: previous and current bus scan bitmaps (16 bytes each) */
|
||||||
|
static __xdata BYTE hp_prev[16]; /* last completed scan */
|
||||||
|
static __xdata BYTE hp_curr[16]; /* working scan buffer */
|
||||||
|
static __xdata WORD hp_changes; /* cumulative device change events */
|
||||||
|
static __xdata BYTE hp_added; /* devices added in last scan */
|
||||||
|
static __xdata BYTE hp_removed; /* devices removed in last scan */
|
||||||
|
static __xdata BYTE hp_scan_ok; /* 1 after first scan completes */
|
||||||
|
|
||||||
|
/* Streaming diagnostics counters */
|
||||||
|
static __xdata DWORD sd_poll_count; /* main-loop poll cycles while armed */
|
||||||
|
static __xdata WORD sd_overflow_count; /* EP2 FULL events detected */
|
||||||
|
static __xdata WORD sd_sync_loss; /* BCM4500 transport sync losses */
|
||||||
|
static __xdata BYTE sd_last_status; /* last BCM4500 status register */
|
||||||
|
static __xdata BYTE sd_last_lock; /* last BCM4500 lock register */
|
||||||
|
static __xdata BYTE sd_had_sync; /* had sync in previous poll */
|
||||||
|
|
||||||
|
/* Main loop timing: USB frame counter for periodic tasks */
|
||||||
|
static __xdata WORD hp_last_frame; /* frame counter at last I2C scan */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* BCM4500 register initialization data extracted from stock v2.06 firmware.
|
* BCM4500 register initialization data extracted from stock v2.06 firmware.
|
||||||
* FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0)
|
* FUN_CODE_0ddd writes these 3 blocks to BCM4500 indirect registers (page 0)
|
||||||
@ -490,6 +512,126 @@ static void bcm4500_shutdown(void) {
|
|||||||
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
|
IOA = (IOA & ~PIN_PWR_EN) | PIN_PWR_DIS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- I2C hot-plug detection ---------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Scan all I2C addresses 0x01-0x77, storing result as 16-byte bitmap
|
||||||
|
* in hp_curr[]. Then compare with hp_prev[] to detect changes.
|
||||||
|
* Must NOT be called while streaming (I2C bus contention with GPIF).
|
||||||
|
*/
|
||||||
|
static void i2c_hotplug_scan(void) {
|
||||||
|
static __xdata BYTE hp_a, hp_byte, hp_bit, hp_diff;
|
||||||
|
|
||||||
|
/* Save current as previous before starting new scan.
|
||||||
|
* This keeps hp_prev valid between scans so the host can read
|
||||||
|
* both bitmaps and see the actual transition. */
|
||||||
|
for (hp_a = 0; hp_a < 16; hp_a++)
|
||||||
|
hp_prev[hp_a] = hp_curr[hp_a];
|
||||||
|
|
||||||
|
/* Clear current scan buffer */
|
||||||
|
for (hp_a = 0; hp_a < 16; hp_a++)
|
||||||
|
hp_curr[hp_a] = 0;
|
||||||
|
|
||||||
|
/* Probe each 7-bit address using timeout-protected I2C ops */
|
||||||
|
for (hp_a = 1; hp_a < 0x78; hp_a++) {
|
||||||
|
I2CS |= bmSTART;
|
||||||
|
I2DAT = hp_a << 1; /* write direction */
|
||||||
|
if (!i2c_wait_done())
|
||||||
|
goto hp_abort; /* I2C hung — abandon scan */
|
||||||
|
if (I2CS & bmACK) {
|
||||||
|
hp_byte = hp_a >> 3;
|
||||||
|
hp_bit = hp_a & 0x07;
|
||||||
|
hp_curr[hp_byte] |= (1 << hp_bit);
|
||||||
|
}
|
||||||
|
I2CS |= bmSTOP;
|
||||||
|
if (!i2c_wait_stop())
|
||||||
|
goto hp_abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compare with previous scan (only after first successful scan) */
|
||||||
|
if (hp_scan_ok) {
|
||||||
|
hp_added = 0;
|
||||||
|
hp_removed = 0;
|
||||||
|
for (hp_a = 0; hp_a < 16; hp_a++) {
|
||||||
|
hp_diff = hp_curr[hp_a] ^ hp_prev[hp_a];
|
||||||
|
if (hp_diff) {
|
||||||
|
/* Count new devices (in curr but not prev) */
|
||||||
|
hp_byte = hp_diff & hp_curr[hp_a];
|
||||||
|
while (hp_byte) {
|
||||||
|
hp_added++;
|
||||||
|
hp_byte &= (hp_byte - 1); /* Kernighan clear-lowest */
|
||||||
|
}
|
||||||
|
/* Count removed devices (in prev but not curr) */
|
||||||
|
hp_byte = hp_diff & hp_prev[hp_a];
|
||||||
|
while (hp_byte) {
|
||||||
|
hp_removed++;
|
||||||
|
hp_byte &= (hp_byte - 1);
|
||||||
|
}
|
||||||
|
/* Count per-device changes (not per-byte) */
|
||||||
|
hp_byte = hp_diff;
|
||||||
|
while (hp_byte) {
|
||||||
|
hp_changes++;
|
||||||
|
hp_byte &= (hp_byte - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hp_scan_ok = 1;
|
||||||
|
return;
|
||||||
|
|
||||||
|
hp_abort:
|
||||||
|
/* I2C timeout during scan — issue STOP and bail out.
|
||||||
|
* hp_curr is partial so we don't update hp_prev. */
|
||||||
|
I2CS |= bmSTOP;
|
||||||
|
last_error = ERR_I2C_TIMEOUT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Streaming diagnostics poll ---------- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Called from main loop while streaming (BM_ARMED set).
|
||||||
|
* Checks EP2 FIFO overflow and BCM4500 transport sync state.
|
||||||
|
*/
|
||||||
|
/* Rate-limit I2C reads: only poll BCM4500 every SD_I2C_INTERVAL polls.
|
||||||
|
* At ~100K main-loop iterations/sec, 4096 ≈ every 40ms — fast enough
|
||||||
|
* for meaningful sync-loss detection without saturating the I2C bus. */
|
||||||
|
#define SD_I2C_INTERVAL 4096
|
||||||
|
|
||||||
|
static void stream_diag_poll(void) {
|
||||||
|
static __xdata BYTE sd_rd[2]; /* dedicated I2C buffer for diag reads */
|
||||||
|
|
||||||
|
/* Saturate at max instead of wrapping (I1 fix) */
|
||||||
|
if (sd_poll_count < 0xFFFFFFFF)
|
||||||
|
sd_poll_count++;
|
||||||
|
|
||||||
|
/* EP2 FIFO full check is a pure SFR read — no I2C, always safe */
|
||||||
|
if (EP2CS & bmEPFULL) {
|
||||||
|
if (sd_overflow_count < 0xFFFF)
|
||||||
|
sd_overflow_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rate-limited BCM4500 I2C reads for sync tracking.
|
||||||
|
* Only attempt if BCM is booted and interval elapsed. */
|
||||||
|
if ((config_status & BM_FW_LOADED) &&
|
||||||
|
((WORD)sd_poll_count & (SD_I2C_INTERVAL - 1)) == 0) {
|
||||||
|
sd_rd[0] = 0;
|
||||||
|
sd_rd[1] = 0;
|
||||||
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_STATUS, 1, &sd_rd[0]);
|
||||||
|
i2c_combined_read(BCM4500_ADDR, BCM_REG_LOCK, 1, &sd_rd[1]);
|
||||||
|
|
||||||
|
sd_last_status = sd_rd[0];
|
||||||
|
sd_last_lock = sd_rd[1];
|
||||||
|
|
||||||
|
/* Detect sync loss: had lock (bit 5) previously, lost it now */
|
||||||
|
if (sd_had_sync && !(sd_last_lock & 0x20)) {
|
||||||
|
if (sd_sync_loss < 0xFFFF)
|
||||||
|
sd_sync_loss++;
|
||||||
|
}
|
||||||
|
sd_had_sync = (sd_last_lock & 0x20) ? 1 : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------- GPIF streaming ---------- */
|
/* ---------- GPIF streaming ---------- */
|
||||||
|
|
||||||
static void gpif_start(void) {
|
static void gpif_start(void) {
|
||||||
@ -1433,8 +1575,8 @@ BOOL handle_vendorcommand(BYTE cmd) {
|
|||||||
|
|
||||||
/* 0x92: GET_FW_VERS -- return firmware version and build date */
|
/* 0x92: GET_FW_VERS -- return firmware version and build date */
|
||||||
case GET_FW_VERS:
|
case GET_FW_VERS:
|
||||||
EP0BUF[0] = 0x00; /* patch -> version 3.03.0 */
|
EP0BUF[0] = 0x00; /* patch -> version 3.04.0 */
|
||||||
EP0BUF[1] = 0x03; /* minor */
|
EP0BUF[1] = 0x04; /* minor */
|
||||||
EP0BUF[2] = 0x03; /* major */
|
EP0BUF[2] = 0x03; /* major */
|
||||||
EP0BUF[3] = 0x0F; /* day = 15 */
|
EP0BUF[3] = 0x0F; /* day = 15 */
|
||||||
EP0BUF[4] = 0x02; /* month = 2 */
|
EP0BUF[4] = 0x02; /* month = 2 */
|
||||||
@ -1701,6 +1843,70 @@ BOOL handle_vendorcommand(BYTE cmd) {
|
|||||||
EP0BCL = 1;
|
EP0BCL = 1;
|
||||||
return TRUE;
|
return TRUE;
|
||||||
|
|
||||||
|
/* 0xBD: GET_STREAM_DIAG -- streaming diagnostics counters
|
||||||
|
* Returns 12 bytes:
|
||||||
|
* [0-3] u32 LE poll_count (main-loop poll cycles while armed)
|
||||||
|
* [4-5] u16 LE overflow_count (EP2 FIFO full events)
|
||||||
|
* [6-7] u16 LE sync_loss (BCM4500 lock-lost transitions)
|
||||||
|
* [8] u8 last_status (BCM4500 register 0xA2)
|
||||||
|
* [9] u8 last_lock (BCM4500 register 0xA4)
|
||||||
|
* [10] u8 armed (1 if currently streaming)
|
||||||
|
* [11] u8 had_sync (1 if currently locked)
|
||||||
|
* wValue=1 to reset counters after read. */
|
||||||
|
case GET_STREAM_DIAG:
|
||||||
|
EP0BUF[0] = (BYTE)(sd_poll_count);
|
||||||
|
EP0BUF[1] = (BYTE)(sd_poll_count >> 8);
|
||||||
|
EP0BUF[2] = (BYTE)(sd_poll_count >> 16);
|
||||||
|
EP0BUF[3] = (BYTE)(sd_poll_count >> 24);
|
||||||
|
EP0BUF[4] = (BYTE)(sd_overflow_count);
|
||||||
|
EP0BUF[5] = (BYTE)(sd_overflow_count >> 8);
|
||||||
|
EP0BUF[6] = (BYTE)(sd_sync_loss);
|
||||||
|
EP0BUF[7] = (BYTE)(sd_sync_loss >> 8);
|
||||||
|
EP0BUF[8] = sd_last_status;
|
||||||
|
EP0BUF[9] = sd_last_lock;
|
||||||
|
EP0BUF[10] = (config_status & BM_ARMED) ? 1 : 0;
|
||||||
|
EP0BUF[11] = sd_had_sync;
|
||||||
|
/* Reset counters if wValue=1 */
|
||||||
|
if (wval == 1) {
|
||||||
|
sd_poll_count = 0;
|
||||||
|
sd_overflow_count = 0;
|
||||||
|
sd_sync_loss = 0;
|
||||||
|
}
|
||||||
|
EP0BCH = 0;
|
||||||
|
EP0BCL = 12;
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
/* 0xBE: GET_HOTPLUG_STATUS -- I2C device change detection
|
||||||
|
* Returns 36 bytes:
|
||||||
|
* [0-15] current I2C bus bitmap (16 bytes)
|
||||||
|
* [16-17] u16 LE change_count (cumulative change events)
|
||||||
|
* [18] u8 devices_added (in last scan)
|
||||||
|
* [19] u8 devices_removed (in last scan)
|
||||||
|
* [20-35] previous I2C bus bitmap (16 bytes)
|
||||||
|
* wValue=1 to reset change counter after read.
|
||||||
|
* wValue=2 to force immediate rescan. */
|
||||||
|
case GET_HOTPLUG_STATUS: {
|
||||||
|
static __xdata BYTE hp_i;
|
||||||
|
if (wval == 2) {
|
||||||
|
/* Force rescan (only when not streaming) */
|
||||||
|
if (!(config_status & BM_ARMED))
|
||||||
|
i2c_hotplug_scan();
|
||||||
|
}
|
||||||
|
for (hp_i = 0; hp_i < 16; hp_i++)
|
||||||
|
EP0BUF[hp_i] = hp_curr[hp_i];
|
||||||
|
EP0BUF[16] = (BYTE)(hp_changes);
|
||||||
|
EP0BUF[17] = (BYTE)(hp_changes >> 8);
|
||||||
|
EP0BUF[18] = hp_added;
|
||||||
|
EP0BUF[19] = hp_removed;
|
||||||
|
for (hp_i = 0; hp_i < 16; hp_i++)
|
||||||
|
EP0BUF[20 + hp_i] = hp_prev[hp_i];
|
||||||
|
if (wval == 1)
|
||||||
|
hp_changes = 0;
|
||||||
|
EP0BCH = 0;
|
||||||
|
EP0BCL = 36;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
@ -1762,6 +1968,21 @@ void main(void) {
|
|||||||
last_error = ERR_OK;
|
last_error = ERR_OK;
|
||||||
got_sud = FALSE;
|
got_sud = FALSE;
|
||||||
|
|
||||||
|
/* Initialize hot-plug detection state */
|
||||||
|
hp_changes = 0;
|
||||||
|
hp_added = 0;
|
||||||
|
hp_removed = 0;
|
||||||
|
hp_scan_ok = 0;
|
||||||
|
hp_last_frame = 0;
|
||||||
|
|
||||||
|
/* Initialize streaming diagnostics */
|
||||||
|
sd_poll_count = 0;
|
||||||
|
sd_overflow_count = 0;
|
||||||
|
sd_sync_loss = 0;
|
||||||
|
sd_last_status = 0;
|
||||||
|
sd_last_lock = 0;
|
||||||
|
sd_had_sync = 0;
|
||||||
|
|
||||||
REVCTL = 0x03; /* NOAUTOARM + SKIPCOMMIT */
|
REVCTL = 0x03; /* NOAUTOARM + SKIPCOMMIT */
|
||||||
SYNCDELAY;
|
SYNCDELAY;
|
||||||
|
|
||||||
@ -1832,5 +2053,33 @@ void main(void) {
|
|||||||
handle_setupdata();
|
handle_setupdata();
|
||||||
got_sud = FALSE;
|
got_sud = FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Periodic tasks using USB frame counter (125us/frame at HS).
|
||||||
|
* USBFRAMEH:USBFRAMEL wraps every 2048ms (16384 frames).
|
||||||
|
* We check every ~8000 frames (~1 second).
|
||||||
|
*
|
||||||
|
* The two SFRs are incremented by USB hardware asynchronously,
|
||||||
|
* so we read H-L-H and retry if H changed (carry propagation). */
|
||||||
|
{
|
||||||
|
WORD cur_frame;
|
||||||
|
BYTE fh;
|
||||||
|
do {
|
||||||
|
fh = USBFRAMEH;
|
||||||
|
cur_frame = ((WORD)fh << 8) | USBFRAMEL;
|
||||||
|
} while (fh != USBFRAMEH);
|
||||||
|
|
||||||
|
/* Streaming diagnostics: poll every main-loop iteration when armed */
|
||||||
|
if (config_status & BM_ARMED) {
|
||||||
|
stream_diag_poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* I2C hot-plug: scan every ~1s, but only when NOT streaming
|
||||||
|
* (I2C bus contention with GPIF would corrupt data) */
|
||||||
|
if (!(config_status & BM_ARMED) &&
|
||||||
|
((WORD)(cur_frame - hp_last_frame) > 8000)) {
|
||||||
|
hp_last_frame = cur_frame;
|
||||||
|
i2c_hotplug_scan();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,6 +63,8 @@ CMD_MULTI_REG_READ = 0xB9
|
|||||||
CMD_PARAM_SWEEP = 0xBA
|
CMD_PARAM_SWEEP = 0xBA
|
||||||
CMD_ADAPTIVE_BLIND_SCAN = 0xBB
|
CMD_ADAPTIVE_BLIND_SCAN = 0xBB
|
||||||
CMD_GET_LAST_ERROR = 0xBC
|
CMD_GET_LAST_ERROR = 0xBC
|
||||||
|
CMD_GET_STREAM_DIAG = 0xBD
|
||||||
|
CMD_GET_HOTPLUG_STATUS = 0xBE
|
||||||
|
|
||||||
# Error codes (returned by CMD_GET_LAST_ERROR)
|
# Error codes (returned by CMD_GET_LAST_ERROR)
|
||||||
ERR_OK = 0x00
|
ERR_OK = 0x00
|
||||||
@ -789,6 +791,68 @@ class SkyWalker1:
|
|||||||
"""Disable east/west soft limits."""
|
"""Disable east/west soft limits."""
|
||||||
self.send_diseqc_message(diseqc_disable_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 ---
|
# --- DiSEqC 1.2 command builders ---
|
||||||
|
|
||||||
|
|||||||
@ -242,3 +242,13 @@ class USBBridge:
|
|||||||
def get_last_error_str(self) -> str:
|
def get_last_error_str(self) -> str:
|
||||||
with self._lock:
|
with self._lock:
|
||||||
return self._dev.get_last_error_str()
|
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)
|
||||||
|
|||||||
@ -576,6 +576,51 @@ class DemoDevice:
|
|||||||
rng = random.Random(start_reg)
|
rng = random.Random(start_reg)
|
||||||
return bytes(rng.randint(0, 255) for _ in range(count))
|
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 ---
|
# --- Internal signal model ---
|
||||||
|
|
||||||
def _power_at(self, freq_mhz: float, elapsed: float) -> float:
|
def _power_at(self, freq_mhz: float, elapsed: float) -> float:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user