First off, thank you for MEDIA_PROTOCOL.md and docs/01-protocol-map.md. They're detailed enough that I wrote a working client from scratch, in a language and on an OS you hadn't touched, without owning any of the reference hardware you did. That's rare.
I used them to build Osmo Offload — a standalone Windows desktop utility (Python + PySide6, MIT) that pulls photos and video off an Osmo wirelessly: BLE pair → read WiFi creds → join the camera AP → DUML datalink for the list + status → HTTP download. No phone, no cable, no DJI account. It does the "get my footage onto my editing PC" job that DJI's phone-only transfer refuses to. There's a compiled release if anyone wants to try it. I mention it partly in case you'd like to point Windows users somewhere — your README's prior art is all Android / Node / macOS-Python, so this may fill a gap.
Everything below is verified on a real Osmo Pocket 4 Pro (BLE OsmoPocket4P, model id 0x0022, the newer advert format — product type 218 at mfr payload bytes 10–11, classic model byte reads 0x0000, exactly as you describe). The README lists the 4/4 Pro as "Works, but I haven't tested it myself," and the per-model table has several ⚠️/unconfirmed cells for the Pro, so here's the data to fill them in.
Confirmed on hardware
- Full pipeline works: BLE pair (token
osmo, on-screen approval once, already-paired fast path), WiFi cred getters 07/07 + 07/0e (12-char passphrase — no saved-password fallback needed here), AP wake, datalink on udp/9004 + TCP-7001 poke, WPA2 / 5.8 GHz. Sustained ~60–63 MB/s downloads over 5 GHz.
- Handle geometry (your table had the Pro as
0x00100000 / 0x40 ⚠️ with no internal base): confirmed and extended —
- internal store base
0x40100000, step 0x40 per file sequence (observed handles 0x40100040, …140, …180, …1c0 for seq 0001/0005/0006/0007)
- SD store base
0x00100000, step 0x40 (handle 0x00100040 for seq 0001 on card)
- Store →
/v2?storage=: SD card → storage=0, internal → storage=1. Per-store split via the request-counter echo works; one list per store.
0x02/0xdc storage: 40-byte two-store body; internal ~103 GB, card 118.8 GB read correctly, 0/0 for an empty slot.
- Manifest
u32-LE count header reads 0 on the Pro (I count records instead). Worth flagging: your notes say the non-Pro Pocket 4 writes a true count — the Pro behaves like the Action 5/6 here and writes 0.
- Stills are deletable by handle, exactly as your delete-handle note predicts ("stills carry one too wherever they carry a marker"). Photo records use the short
[fe|ff] 19 06 marker; reading the handle at (19 06) − 10 and size at (19 06) − 14 works for both photos and video. 0x00/0x28 returned 0x0000 and a re-list confirmed the right file was gone, for .JPG stills and .MP4 alike.
- Write-window aging (~40–70 s, your Nano/Edge Pro finding): reproduces on the Pro — deletes silently stop landing on an aged registered session; re-registering before each write fixes it.
- Stale-session-hold (your "Pocket 4 / 3" quirk): hit it, recover by re-handshake / reopen. Confirmed.
- BLE MTU: WinRT negotiated 510 and the camera answered fine — a concrete data point next to your "500 good / 517 breaks it" note.
One practical gotcha for implementers
A manifest decoder that only searches for the 4-byte video marker 03 ff 19 06 will decode handle = 0 (and size = 0) for every still, because the Pro's photos use the shorter 3-byte [fe|ff] 19 06 form. That silently makes stills undeletable and sizeless. Anchoring on the 19 06 pair (with a fe/ff guard byte) instead of the full video marker fixes it for both record shapes — this cost me an afternoon, so it might be worth a line in the docs.
I didn't exercise favourites/star or the low-res proxy on this body, so those two ⚠️ cells I can't speak to.
Happy to open a PR against the per-model tables with the confirmed values if that's easier than transcribing from here. Thanks again — this was a genuinely fun build.
First off, thank you for
MEDIA_PROTOCOL.mdanddocs/01-protocol-map.md. They're detailed enough that I wrote a working client from scratch, in a language and on an OS you hadn't touched, without owning any of the reference hardware you did. That's rare.I used them to build Osmo Offload — a standalone Windows desktop utility (Python + PySide6, MIT) that pulls photos and video off an Osmo wirelessly: BLE pair → read WiFi creds → join the camera AP → DUML datalink for the list + status → HTTP download. No phone, no cable, no DJI account. It does the "get my footage onto my editing PC" job that DJI's phone-only transfer refuses to. There's a compiled release if anyone wants to try it. I mention it partly in case you'd like to point Windows users somewhere — your README's prior art is all Android / Node / macOS-Python, so this may fill a gap.
Everything below is verified on a real Osmo Pocket 4 Pro (BLE⚠️ /unconfirmed cells for the Pro, so here's the data to fill them in.
OsmoPocket4P, model id0x0022, the newer advert format — product type218at mfr payload bytes 10–11, classic model byte reads0x0000, exactly as you describe). The README lists the 4/4 Pro as "Works, but I haven't tested it myself," and the per-model table has severalConfirmed on hardware
osmo, on-screen approval once, already-paired fast path), WiFi cred getters07/07+07/0e(12-char passphrase — no saved-password fallback needed here), AP wake, datalink on udp/9004 + TCP-7001 poke, WPA2 / 5.8 GHz. Sustained ~60–63 MB/s downloads over 5 GHz.0x00100000/0x400x40100000, step0x40per file sequence (observed handles0x40100040,…140,…180,…1c0for seq 0001/0005/0006/0007)0x00100000, step0x40(handle0x00100040for seq 0001 on card)/v2?storage=: SD card →storage=0, internal →storage=1. Per-store split via the request-counter echo works; one list per store.0x02/0xdcstorage: 40-byte two-store body; internal ~103 GB, card 118.8 GB read correctly,0/0for an empty slot.u32-LEcount header reads0on the Pro (I count records instead). Worth flagging: your notes say the non-Pro Pocket 4 writes a true count — the Pro behaves like the Action 5/6 here and writes0.[fe|ff] 19 06marker; reading the handle at(19 06) − 10and size at(19 06) − 14works for both photos and video.0x00/0x28returned0x0000and a re-list confirmed the right file was gone, for.JPGstills and.MP4alike.One practical gotcha for implementers
A manifest decoder that only searches for the 4-byte video marker
03 ff 19 06will decodehandle = 0(andsize = 0) for every still, because the Pro's photos use the shorter 3-byte[fe|ff] 19 06form. That silently makes stills undeletable and sizeless. Anchoring on the19 06pair (with afe/ffguard byte) instead of the full video marker fixes it for both record shapes — this cost me an afternoon, so it might be worth a line in the docs.I didn't exercise favourites/star or the low-res proxy on this body, so those two⚠️ cells I can't speak to.
Happy to open a PR against the per-model tables with the confirmed values if that's easier than transcribing from here. Thanks again — this was a genuinely fun build.