Skip to content

Latest commit

 

History

History
101 lines (80 loc) · 4 KB

File metadata and controls

101 lines (80 loc) · 4 KB

The vendor protocol (V3 frames over the UVC extension unit)

Same wire format as the Tiny 2 — credit to lxman/obsbot-mcp for working it out. The commands differ (see ai-modes.md), the framing does not.

Addressing

USB VID / PID 0x3564 / 0xFEF8
XU descriptor GUID {9A1E7291-6843-4683-6D92-39BC7906EE49}
Control selector 0x02
Payload 60 bytes, zero-padded

On Windows: IBaseFilter → IKsTopologyInfo, find the node whose type is KSNODETYPE_DEV_SPECIFIC ({941C7AC0-C559-11D0-8A2B-00A0C9255AC1}), CreateNodeInstance for IKsControl, then KsProperty with a KSP_NODE whose Property.Set is the XU descriptor GUID above — not the node's type GUID. Using the node type GUID is the classic mistake and yields ERROR_SET_NOT_FOUND (0x80070492).

Some drivers only dispatch node properties through the filter's IKsControl rather than the per-node one. If you get ERROR_SET_NOT_FOUND, retry via filter.QueryInterface(IKsControl).

Frame layout

off 0     0xAA                      magic
off 1     flags   0x25 = SET (with nested payload)
                  0x01 = header-only GET   <-- required for reads
off 2-3   seq     u16 LE            increments per frame
off 4-5   len     u16 LE = 12       bytes covered by the header token
off 6-7   token   u16 LE            CRC-16/USB over frame[0:12], token zeroed
off 8     sender    = 0x0A          (host)
off 9     receiver  = 0x02 camera / 0x03 gimbal / 0x04 AI
off 10-11 cmd     u16 LE
off 12+   nested payload segment, when payload is non-empty:
            len2  u16 @12
            tok2  u16 @14   CRC-16/USB over frame[12 : 16+len2], tok2 zeroed
            data       @16

CRC-16/USB

Poly 0xA001 (reflected 0x8005), init 0xFFFF, refin/refout true, xorout 0xFFFF.

def crc16_usb(data: bytes) -> int:
    crc = 0xFFFF
    for b in data:
        crc ^= b
        for _ in range(8):
            crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
    return crc ^ 0xFFFF

Reading state — the part that makes verification possible

GETs only work with frame[1] = 0x01 and an empty payload. A GET sent with the 0x25 SET flavour is never answered — which is what leads people to conclude the read path is broken.

Sequence: write the GET frame to selector 0x02, wait ~100 ms, then read selector 0x02. A real answer starts aa 29; all-zero means no reply.

xu_set(2, build_frame(seq, 0x0104, 0x04, b"", flags=0x01))  # AI_GET_QUICK_STATUS
time.sleep(0.1)
reply = xu_get(2, 60)
n = int.from_bytes(reply[12:14], "little")
payload = reply[16:16+n]

Confirmed answering on Tiny 3: AI_GET_QUICK_STATUS 0x0104, AI_GET_GIM_STATE 0x6604, AI_GET_HAND_TRACK_STATE 0x2004, AI_GET_CONTROL_PARAMETER 0x5484, AI_GET_GIMBAL_PARAMETER 0x3F84.

Commands worth knowing

Purpose cmd receiver payload
Gimbal velocity 0x6484 0x04 roll, pitch, yaw — 3× f32 LE, deg/s (yaw inverted)
Gimbal absolute 0x6444 0x04 roll, pitch, yaw — 3× f32 LE, motor degrees
Recenter 0x00C3 0x03 6 zero bytes
Sleep / wake 0xA0C2 0x02 u32: 0 wake, 1 sleep
AI engine on/off 0x0244 0x04 u32 0/1
Tracking on (+ mode) 0x0584 0x04 u32 subject, u32 view
Tracking off 0x0504 0x04 (empty)
AI mode 0x0284 0x04 u32 0–5 — see ai-modes.md
Read AI state 0x0104 0x04 (empty, flags 0x01)

Zoom is not here — use standard UVC CT_ZOOM_ABSOLUTE. The vendor zoom-ratio command (0x6884) is ignored by this firmware.

Hazards

The Tiny 2 spec warns that some command families can drop the device off the USB bus. Steer clear of boot-position, firmware/upgrade and media families when experimenting. tools/sweep.py skips them by default.

Also: do not open the camera from two processes at once. Concurrent KsProperty calls hard-crash one of them with no Python traceback.