"""frame.py -- the USB-CDC wire protocol shared with OpenController/deck_input.cpp. frame = [0xA5][0x5A][type][len][payload(len)][sum8(type,len,payload...)] The reader resynchronizes on the 2-byte sync word, so any stray bytes (a boot banner, a serial monitor) are skipped harmlessly. Keep this file in lock-step with deck_input.{h,cpp}. """ import struct SYNC0 = 0xA5 SYNC1 = 0x5A # Deck -> nRF T_INPUT = 0x01 T_CONTROL = 0x02 T_REQ_STATUS = 0x03 # nRF -> Deck T_STATUS = 0x10 T_TEXT = 0x1F # nRF -> Deck haptic: [intensity u16][left u16][right u16][lgain u8][rgain u8] (Deck rumble cmd 0xEB) T_HAPTIC = 0x11 # INPUT payload: buttons u32, lx ly rx ry s16, lt rt u8, lpx lpy rpx rpy s16, lpp rpp u16, axyz/gxyz s16. # 4 + 8 + 2 + 8 + 4 + 12 = 38 bytes, little-endian. Matches applyInput() in deck_input.cpp. _INPUT = struct.Struct(" an INPUT frame.""" g = st.get if isinstance(st, dict) else (lambda k, d=0: getattr(st, k, d)) payload = _INPUT.pack( g("buttons", 0) & 0xFFFFFFFF, g("lx", 0), g("ly", 0), g("rx", 0), g("ry", 0), g("lt", 0) & 0xFF, g("rt", 0) & 0xFF, g("lpx", 0), g("lpy", 0), g("rpx", 0), g("rpy", 0), g("lpp", 0) & 0xFFFF, g("rpp", 0) & 0xFFFF, g("ax", 0), g("ay", 0), g("az", 0), g("gx", 0), g("gy", 0), g("gz", 0), ) return build(T_INPUT, payload) def parse_haptic(payload): """Decode a T_HAPTIC payload -> dict for the Deck rumble cmd. Mirrors deck_input.cpp.""" if len(payload) < 8: return None intensity, left, right = struct.unpack_from(" len(payload): break slot = payload[off] alive = payload[off + 1] puuid = struct.unpack_from(" 240: self._flush_text(out) elif st == 1: self._st = 2 if c == SYNC1 else (1 if c == SYNC0 else 0) elif st == 2: self._type = c self._sum = c self._st = 3 elif st == 3: self._len = c self._sum = (self._sum + c) & 0xFF self._buf = bytearray() self._st = 5 if c == 0 else 4 elif st == 4: self._buf.append(c) self._sum = (self._sum + c) & 0xFF if len(self._buf) >= self._len: self._st = 5 elif st == 5: if c == self._sum: out.append((self._type, bytes(self._buf))) self._st = 0 return out