DJI FPV (WM170) DUML communication & vision module binding #474
Replies: 1 comment
Ground-side companion data: DJI FPV Remote Controller 2 (
|
| Intf | Class | Role | Endpoints |
|---|---|---|---|
| 0 | 0xe0/01/03 |
Wireless controller (RNDIS ctrl) | IN 0x82 int |
| 1 | 0x0a |
CDC Data (secondary) | bulk 0x01/0x81 |
| 2 | 0x08/06/50 |
Mass Storage (SCSI) | bulk 0x02/0x83 |
| 3 | 0xff/43/01 |
Vendor — same shape as the goggles video endpoint | bulk OUT 0x03 / IN 0x84 |
| 4 | 0x02/02/01 |
CDC ACM control | IN 0x86 int |
| 5 | 0x0a |
CDC Data — DUML serial (cu.usbmodem*) |
bulk 0x04/0x85 |
| 6 | 0x03 |
HID gamepad | int OUT 0x05 / IN 0x87 (1024B) |
This lines up with what you saw on WM170: interface 3 bulk (0x03/0x84) writes
"succeed" but IN reads time out, and the CDC path only carries telemetry/identity.
What DJI Assistant 2 actually sends to the RC (Windows sniff)
Sniffed DJIService.exe by hooking NtWriteFile/NtReadFile/NtDeviceIoControlFile
in ntdll — hooking at kernel32 was not enough (it sees ReadFile but zero
WriteFile, and never SetCommState; DJIService reaches the port through a
wrapper that bypasses those exports, so ntdll is the single funnel).
Assistant's entire vocabulary on the CDC port is: Version Inquiry,
Activate Config (0x00/0x32), Set Date/Time, Set Country Code (0x00/0x63),
0x00/0x37 (see below), Get Cfg File (0x00/0x4f), plus the RC's 0x06/0x1e
battery heartbeat. Zero simulator (0x0b) traffic, and nothing in set 0x06
beyond the heartbeat — no 0x06/0x24, no 0x06/0x26, no channel commands. So
capturing Assistant is not a route to stick-injection commands on this RC.
A couple of commands missing from the current dissector tables
General 0x00/0x63= Set Country Code. Absent fromGENERAL_UART_CMD_TEXT
(table jumps0x62->0xef). Assistant sends it once at startup with a 2-byte
ASCII region code; RC ACKs status 0.TX 550f04a22aa616274000 63 5553 payload "US" RX 550e0466a62a1627c000 63 00 status 0General 0x00/0x4fGet Cfg File payload format (named but no payload
dissector). Request =[u8 op=1][u32 LE offset][u32 LE max]; reply =
[u8 status][u32 LE chunk_len][u32 LE remaining_after_chunk][data]. Device
picks a 256-byte stride regardless of the 1000 requested. Data concatenates to
a DJI package (IM*H@0,PRAK@40,CNFG@96, then the device-table XML).General 0x00/0x37, unidentified, correlates with Assistant logging
DOI Auth Failed(step-1 timeout). Constant 3-byte payload23 05 00, sent
6x each to two modules, never answered by this firmware. Looks like the first
step of an auth/entitlement handshake the RC doesn't implement. (Note0x37is
taken in the RC set asEnter App Mode Request; this is the General set.)
Negative results that might save someone time
- The RC is not a transparent DUML router to the aircraft. With the aircraft
powered and linked, I sweptVersion Inquiryacross all device types 0-31 x
index 0-7 (248 addresses, 2 source addrs x 2 request types). Only ground-side
modules answer (0x06MCU150,0x0eHDVT,0x1f/0x2dRC150,0xa6serial).
No air-side device replied at any address — not FC (type 3), not air HD
link/DM36x (9/8), not gimbal/cam/ESC/batt/IMU. A passive listen shows only the
RC's own0x06/0x1epush. So you can't reach the FC by addressing it and
letting the RC forward. DJI P1 HDVT(0x0e) does not implement HD Link set0x09. 24 argument-
free getters: 20 return0xe0(unsupported), 4 silent. Clean rejection (it
answers Version Inquiry and returns explicit error codes), just no0x09
support — consistent with the table describing the Lightbridge/OcuSync-era
stack, not this SDR.- Classic injection writers all fail on CDC, aircraft linked, props off:
0x06/0x24Sim Flight Mode,0x06/0x26AETR Push, Center Virtual RC
0x05/0x00+0x01, Special0x01/*, FlyC App Joystick0x03/0x8E. Every one
is silent or0xe0, against MCU150, HDVT,0xa6, RC150, FC and Center Board.
Warning: MCU150 (0x06) 0x60–0x6c is a soft-brick range
There's an undocumented cluster on MCU150 (0x06/0x60..0x6c) that ACKs and
makes the RC beep / flash its LEDs. It's a trap: repeated writes there (and the
empty-payload form of 0x06/0x19 Control Mode Set, and empty GETs on
0x6d–0x6f) wedge MCU150 — it stops answering while every other module
still replies 3/3, the power button and the four white LEDs go dead, and the USB
gadget re-enumerates. Recovery is a power cycle; nothing persisted (stick
calibration 0x06/0x38 came back byte-identical). Once wedged, 0x06 returns
SILENT to everything, which is easy to misread as "not implemented" — interleave
a liveness check when probing that address.
My open questions (same as yours, from the RC side)
- What state or sequence unlocks the vendor bulk interface (intf 3,
0xff/43/01,0x03/0x84)? It's the same descriptor shape as the goggles
video endpoint; the goggles start streaming after anRMVTwrite, but on the
RC every write to0x03(includingRMVT) times out and IN stays quiet. Is
there a known enable/handshake, or does it require a specific device mode
(e.g. Assistant-connected, or a prior control-transfer)? - You mention all Assistant traffic showing on "EP 8" in USBPcap with no
matching descriptor — is that the RNDIS/interface-0..1 path, and does
Assistant's bidirectional command channel actually run over RNDIS/IP rather
than the CDC ACM DUML port? - Is there a known handshake/auth that opens the command channel on this
generation, and isGeneral 0x00/0x37("DOI Auth") the gate? - Does WM170/rc150 need a custom USB driver beyond WinUSB/libusb for that
vendor interface to accept data?
Happy to share raw captures / the pyusb probing tools if useful. Everything above
is a single rc150 unit on firmware 01.00.0606; command labels for 0x63/0x37
are inferred from payload + Assistant logs, not firmware disassembly.
Uh oh!
There was an error while loading. Please reload this page.
DJI FPV (WM170) - DUML communication protocol & vision module binding
Problem
Replaced the visual sensor module on a DJI FPV (WM170). After replacement, cameras are not detected. DJI Assistant 2 shows "Calibration" as an option but it never starts (infinite loading). The module is physically connected and responds in some capacity, but is UNPAIRED (encryption state = UNPAIRED).
What we've tried
We built a Python tool using
pyusb+libusbthat implements the DUML protocol based on dji-firmware-tools.Hardware setup
DJI FPV USB descriptor (VID 0x2CA3, PID 0x001F):
Communication attempts
Via COM6 (serial): Only receives one-way telemetry. Drone streams 4 packet types:
cmd_set=0x02, cmd_id=0x80(telemetry, ~10Hz, 73 bytes)cmd_set=0x03, cmd_id=0x43(telemetry, ~5Hz, 81 bytes)cmd_set=0x00, cmd_id=0x81(FC version broadcast "WM170", ~1Hz, 77 bytes)cmd_set=0x00, cmd_id=0xF1(status, ~2Hz, 17 bytes)Via BULK Interface (EP 0x03 OUT / EP 0x84 IN):
Via CDC ACM direct (EP 0x04 OUT / EP 0x85 IN) using libusb:
Combined BULK OUT + CDC IN: Same result, no command responses
USB capture analysis (DJI Assistant 2 + USBPcap/Wireshark)
Successfully captured a session where DJI Assistant 2 FPV was connected. Key findings from the pcap:
DJI Assistant 2 handshake sequence:
FC parameter access (after handshake):
cmd_set=0x03, cmd_id=0xE0 payload=00 00→ FC responds with00 00 00 00 c7 3c 13 c3 98 05 00 00(0x0598 = 1432 params)cmd_set=0x03, cmd_id=0xE5→ extended param infocmd_set=0x03, cmd_id=0xE1 payload=00 00 XX 00→ param info query with indexcmd_set=0x03, cmd_id=0xE2 payload=idx(2)+value(4)→ param read/writecmd_set=0x03, cmd_id=0xE6→ status/telemetry group query (payloads include ASCII names like "g_status.rc.current_mode_switch")cmd_set=0x03, cmd_id=0xE9→ progress/status indicators (countdown from 0xFF to 0x00)FC responses in the capture include ASCII parameter names:
Questions
Environment
All reactions