Your DJI Osmo — or, new in 1.2, your GoPro — starts recording the moment you arm, and stops when you disarm. The camera's own state — recording, clip time, battery, card remaining — is drawn onto your Betaflight OSD, so you know it is rolling before you leave the ground. And if it is not rolling, the OSD says so in as many words rather than leaving you to notice.
Save up to four cameras and it takes whichever one is switched on, in the order you put them in — a Nano on the small quad, a 360 on the big one, no setup in between.
Everything is set up from your phone over Wi-Fi. After the first flash you never need a cable again, including for firmware updates — and the first flash is a web page that works out which chip you have.
| Module | An ESP32-C3 Supermini. This is the only board officially supported. |
| Camera | DJI Osmo Nano, Osmo 360 or Osmo Action — or a GoPro: HERO9 through HERO13 Black, HERO11 Black Mini, MAX 2, MISSION 1 and MISSION 1 Pro. |
| Flight controller | Betaflight 2025.12 or newer, with one spare UART. This is a hard requirement — older firmware cannot draw the OSD text. |
| A phone | For setup. Any browser. |
The module draws about 80 mA from the flight controller's 5 V rail.
Antenna. The board's printed antenna has to stick out past the frame. Carbon fibre blocks 2.4 GHz completely, and no setting compensates for an antenna sealed inside a carbon box.
Four wires to a spare UART:
| Module | Flight controller |
|---|---|
5V |
5V |
GND |
GND |
GPIO4 |
UART RX |
GPIO5 |
UART TX |
Transmit goes to receive, and the grounds must be common. In Betaflight's Ports tab, no peripheral is needed on that UART — just leave it free.
Do not use GPIO2, GPIO8 or GPIO9. They decide how the chip boots, and anything pulling on them at power-up stops it starting at all.
Never plug the module into USB with the flight battery in. The shared 5 V wire ties them together, so USB back-feeds the flight controller and everything else on that rail. Two supplies in parallel through one wire is how regulators die. Battery out first, every time.
You only do this once. After that, updates happen over Wi-Fi from your phone.
The easiest way is the browser tool below — it works out which chip is on your
board and installs the right firmware for it, so you never have to know which
file is yours. If you would rather do it by hand, grab
slate-<version>-<chip>-full.bin from Releases.
Which chip? If you bought a C3 Supermini, which is what this ships on, it is
esp32c3. There is no single file that works on all of them: an image carries
the chip it was built for in its header, and both the module's bootloader and
its own updater refuse a mismatch rather than half-flashing something that will
not boot.
The C3 Supermini is the reference board; the other five are in beta. Two of them are also louder: the C6 and C61 radios stop at −15 dBm and the original ESP32 at −12 dBm, where the C3 reaches −24 dBm — so the quietest setting on those parts still transmits more next to your receiver than the C3 does. If you fly one, tell us how it went.
https://wingeratb.github.io/theslate/
Nothing to download and nothing to choose. The page reads which chip is on your board and installs the firmware for it:
- Plug the module into your computer with a USB-C cable, with the flight battery out.
- Open the link in Chrome or Edge — it uses Web Serial, which Safari and Firefox do not have.
- Click Connect and pick your module from the list the browser shows.
That is the whole job. When it finishes, unplug it and wire it to your flight controller.
It offers two choices. First time, or recovery installs everything and erases your settings, which is what you want on a new board. Update installs new firmware and keeps your saved cameras and settings — though you can do that from your phone over Wi-Fi instead, with no cable at all.
Nothing is uploaded anywhere. The page runs in your browser and talks to the cable, and the firmware it serves is built by the same workflow that publishes the page, from the same source as the release.
If no device appears: hold the module's BOOT button, tap RESET, then release BOOT, and click Connect again. Some boards need this the first time. A cable that only carries power and not data will also show nothing.
If you would rather work in a terminal, the same file can be written with
Espressif's command-line esptool — installed with pip install esptool, or
downloaded as a standalone binary from
its releases page if you would
rather not have Python involved.
Either way you are writing the same -full.bin to offset 0x0, with --chip
set to the chip that file was built for. The browser tool above does exactly
this and asks fewer questions, so reach for the terminal only if you already
know you want to.
Every release also ships a -update.bin. That one is the app on its own,
and it is what the settings page installs over Wi-Fi. It is not
interchangeable with -full.bin: written at 0x0 it will not boot, because it
has no bootloader in front of it.
Use -full.bin for a cable. Use -update.bin for updates from your phone.
Both carry the chip in their name, and the updater checks it: hand it an image built for a different chip and it refuses before writing a byte, rather than staging something that cannot boot.
Everything below is set from a page your phone opens. There is no app, no account and no cable.
Plug it into a computer. A module on a USB cable is being set up, not flown, so it comes up in setup on its own — no button hold, nothing to remember. It is the computer that does it rather than the 5 V: the module looks for the frame packets a USB host sends every millisecond, which a charger, a power bank and your flight controller's own 5 V pad never send. Wired to an FC it boots normally, every time.
Or hold the module's button for ten seconds. It reboots, the LED goes dark, and
an open Wi-Fi network called SLATE-XXXX appears — the last four characters
come from the board itself, so two modules in the same field are never confused.
Join it and the page opens on its own. If your phone does not offer it, go to
http://192.168.4.1/.
Once the module is buried in a frame and you cannot reach the button, use the setup switch instead — see below. Setup is refused while armed, and arming leaves it.
You do not need the flight controller powered to change settings. You do need it if you want to watch a channel move while you pick one.
Nearby is a live list of what is in range, with signal bars. Pick yours and confirm the four-digit code the camera shows. That pairing is remembered across reboots, so this is a once-per-camera job.
The list stays in the order cameras were first seen. It does not re-sort itself as signal changes — a list that reorders under your finger is worse than one that is merely unsorted.
If the camera you want is not listed, it is off, asleep, or already connected to a phone.
The module holds up to four cameras, and they appear above the Nearby list with a number on the right. On power-up it connects to the first one in that order that is actually switched on — so a Nano on the small quad and a 360 on the big one can both live on the same module, and you swap by switching one on and the other off rather than by opening this page.
Drag a card to change the order. Top is 1. Put the camera you fly most at the top; it wins whenever both are on.
Two things worth knowing:
- It never swaps mid-flight. Once connected, that camera keeps the link until it goes away. A higher-priority camera switching on later does not take the link — ending a recording to satisfy a preference would be worse than flying the camera you already have.
- Saving more cameras does not slow it down. One scan sees every camera that is switched on, and the module picks from that — it does not try them one at a time, so four saved cameras connect as quickly as one.
Tap a card to forget that camera. It asks first. Forgetting only removes it from this module — nothing on the camera itself changes — but to add it back it has to be switched on and in range again.
Updating from a firmware that only held one camera keeps it, as the first entry.
A GoPro will not appear in Nearby until it has been put into pairing mode once: on the camera, Preferences › Connections › Connect Device › GoPro Quik App. Do that, then pick it from the list. There is no code to confirm — it simply pairs — and after that first time the camera remembers the module and you never open that menu again.
Supported: HERO9 Black through HERO13 Black, HERO11 Black Mini, MAX 2, MISSION 1 and MISSION 1 Pro. Other models are refused when you try to add them.
On the OSD a GoPro is labelled by generation — GP12, GP13 — the way an Osmo
is NANO or O360; a MISSION 1 is MSN.
The MISSION 1 series speaks a newer variant of GoPro's status protocol than the HERO line. The module knows both, picks by model, and lets the camera's own answer overrule it — so a GoPro newer than any it has heard of still gets asked in both dialects before anyone gives up.
What starts the clip. Four modes:
| Mode | What happens |
|---|---|
| Arm | Arm starts recording, disarm stops it. |
| Switch | An RC channel controls recording; the arm switch is ignored. |
| Cut (default) | Arm starts the clip. A press cuts the current take and immediately starts a fresh one — it never leaves you not recording, it just breaks the footage into separate files. |
| Both | Arm starts and disarm stops, and the switch works independently on top: press mid-flight to stop, press again to start. Arming or disarming takes control back, so you always land on a stopped camera. |
On a module that has never had a switch configured, Cut behaves exactly like Arm.
Control type. How your channel is read — this matters, and the right answer depends on your transmitter:
- Switch — recording runs while the channel sits inside the window below. Position-driven, so after a flight-controller reboot or a brownout the module can look at the switch and know what you meant. Use this for a normal two- or three-position toggle.
- Button — every movement of the channel counts as one press, and each press flips recording. The window is ignored. Use this for a momentary button, which springs back and so has no position to test. The trade-off is that after a brownout the module cannot recover what the state was.
Channel. Any of AUX1–AUX14. It does not need to be mapped to anything in Betaflight's Modes tab — the module reads the channel directly.
Records above / …and below. The microsecond window that counts as "on", anywhere from 900 to 2100 µs. You do not have to know your numbers: move the switch and watch the live marker, then drag the two handles around where it lands. Recording runs while the marker is inside the shaded band.
Leave a little margin either side. Setting the edges exactly on the resting value works until a trim or a rate change moves it a few microseconds.
Not on the settings page, but worth knowing. The button has exactly two gestures:
- A tap toggles recording, whatever the flight controller is doing. It is a test button — the quickest way to confirm on the bench that the camera is paired and listening, without arming anything.
- A ten second hold opens setup, as above.
0 to 30 seconds. Off by default.
When the flight controller stops the recording — you disarm, or you crash — the camera carries on for this long before actually stopping. A crash disarms the quad, and the seconds you want are the ones after that: where it went, and what it landed on.
Arming again during the countdown simply carries on recording, so it costs you
nothing on a normal landing. The OSD counts down (REC 5, REC 4…) so you can
see it happening rather than wondering why the camera is still running.
Post-roll is what broadcast calls the padding at the tail of a recording. Its opposite — pre-roll, the seconds captured before the trigger — is something the camera itself can do, and is not wired up here yet.
Pressing the module's button or your record switch stops immediately. This only ever delays the automatic stop, never you. It is also dropped the moment the flight controller link or the camera goes away — a dead UART still closes an open clip, exactly as before.
Warning is an OSD field like any other — drag it onto a row of its own and put that row wherever you want it, the way a flight controller puts its warnings across the middle of the screen. It is blank space until something is wrong.
If you have not given it a row, the state field carries warnings instead, so a
module updating to this firmware does not silently lose them. Give warnings a
row and the state field goes back to plain REC / IDLE.
The module's LED also blinks fast when it has been asked to record and is not. The most important warning wins:
| Means | |
|---|---|
NO SD |
No card, or the card is full. Nothing can be recorded. |
CAM HOT |
Too hot to record. |
NOT REC |
You asked it to record and it is not. |
BAT LOW |
Camera battery at or below your threshold. |
SD LOW |
Card time at or below your threshold. |
It blinks rather than sitting there, and on the off-beat you still see whether it is recording — an alarm should not cost you the reading it is warning about.
NOT REC waits three seconds before it appears. Starting a recording takes a
Bluetooth round trip plus the camera's own shutter delay, and a warning that
fired on every single arm is one you would stop reading.
Nothing is ever warned about from a reading the camera has not confirmed. A
camera that has gone quiet is not a camera that is refusing, and the state field
already says NO CAM for that.
Warn below camera battery and Warn below card time left set the last
two. Either at zero switches that warning off. NOT REC has no threshold and
cannot be switched off — it is the reason this module exists.
Low, Medium, High, Max. How loudly the module talks to the camera. Low is the flight default.
Turn it down while flying. With this on, the module uses your chosen setting while disarmed and drops to Low for as long as the quad is armed. Setting up wants reach — the camera may be across the room. Flying does not: the module is centimetres from the camera and millimetres from your receiver, where being loud buys nothing and costs the link that actually matters.
Turn this on and you can leave the setting on High without carrying it into the air.
Binds this settings page to an AUX channel, for a module you cannot physically reach.
Channel. Any of AUX1–AUX14. Pick one you are not using for anything else.
Control type. Switch holds for a set time; Button enters instantly on any movement of the channel.
Hold for. Zero to ten seconds, for switch mode. How long the switch must be held up before the module is willing to open setup.
The gesture is narrated on your OSD so you are never guessing:
| The OSD says | Meaning |
|---|---|
ENTER |
The switch is up and the hold has started. |
READY |
The hold is complete. Drop the switch now to enter setup. |
CONFIG |
You are in setup. The camera link is down and the Wi-Fi page is up. |
Let go before READY and nothing happens. The switch only ever opens setup —
you leave with Done & restart at the bottom of the page, or by arming.
Nothing else on the aircraft will drop you out of the page while you are using
it.
Setup is refused while armed, and arming leaves it. A module in setup has no camera link at all, so an aircraft that took off in that state would record nothing and say nothing about why.
Not a setting — a readout. It shows whether the MSP link is up and whether the module currently sees you as armed. Use it to confirm your wiring and your UART before you go looking for other problems.
Drag the fields you want into four rows. Each row is one of Betaflight's four custom messages and holds 16 characters; the bar shows how much of that a row has spent.
| Field | Looks like | Width |
|---|---|---|
| State | NO CAM |
7 |
| Clip time | 12:34 |
7 |
| Battery | BAT 87% |
8 |
| Battery % | 87% |
4 |
| Card left | SD 1H49 |
8 |
| Card | 1H49 |
5 |
| Alive dot | . |
1 |
| Camera | O360 |
4 |
| Warning | NOT REC |
7 |
State is the field to give a row to if you only pick one:
| It says | Meaning |
|---|---|
REC |
The camera is recording. |
IDLE |
The camera is connected and not recording. |
NO CAM |
No camera is talking to the module. |
REC 5 |
Post-roll: still recording, counting down to the stop. |
ENTER / READY / CONFIG |
The setup switch gesture, as above. |
Warning is the other field worth a row. Give it one and it sits blank until
something is wrong, then flashes NOT REC, NO SD, CAM HOT, BAT LOW or
SD LOW — so you can put it across the middle of the screen where a flight
controller puts its own warnings. If you do not give it a row, the State field
carries those words instead. See Warnings.
Widths are worst cases, not what is on screen right now: a battery reading books
room for 100% even while it says 87%, and the clip timer books room for a
long flight. That is why the bar can look fuller than the text appears.
The alive dot blinks once a second while the module is talking to the camera. If it stops, the module has stopped, and that is worth knowing at a glance.
The camera field shows which camera is bound — NANO, O360, A5 — and
disappears with the camera if it goes away.
Choose a -update.bin from the Releases page and install it. See
Updating.
Done & restart at the bottom writes everything and reboots the module back into flying mode. Nothing is applied until you do.
Open the settings page, choose the -update.bin from
Releases, and install. The module keeps the previous firmware
and rolls back to it by itself if the new one fails to run, so a bad update does
not leave you with a dead module inside a frame.
Your camera pairing and settings survive updates.
The Osmo Action series and the Osmo 360 speak DJI's R SDK protocol, and this project's implementation of it derives from DJI's own published Osmo-GPS-Controller-Demo. The Osmo Nano speaks a different protocol, and the module works out which one your camera uses when you pick it — you never have to know.
Attribution and the terms that came with DJI's code are in NOTICE. That component stays under its original MIT licence.
GoPro is the other way round: GoPro publish their Bluetooth interface as Open GoPro, and the GoPro side of this project is written to that document and checked against GoPro's own SDK. It shares the radio with the DJI code and nothing else — a GoPro never touches a line of the DJI protocol, and a DJI camera never touches a line of GoPro's.
PolyForm Strict 1.0.0. The source is here to be read and run: inspect it, build it, flash it on your own module, for any noncommercial purpose. Hobby projects, personal builds, study and experiment are all expressly permitted.
What the licence does not grant is the right to modify it, to redistribute it, or to use it commercially — including selling hardware built on it.
That is deliberate rather than unfriendly. Publishing the source means you can check what runs on your aircraft instead of taking my word for it, which is the point. It does not mean handing someone a head start on selling the same thing.
If you want to do something the licence does not cover — a modification you need, a commercial use, anything — ask. The answer is often yes.
One part is different: components/dji_link/ derives from DJI's own published
demo and stays under its original MIT licence. See NOTICE.
1.2.0. Everything from 1.0 is stable: on an Osmo Nano and an Osmo 360 — pairing, live camera status, every record mode, the OSD, the AUX trigger as both a switch and a momentary button, and firmware updates with rollback.
New since 1.0 is in beta. All of it builds, and the logic is covered by host tests that compile the shipped source rather than a copy of it:
| State | |
|---|---|
| Warnings on the OSD and the LED | beta |
| Keep recording after landing | beta |
| More than one camera, in priority order | beta |
| Five more chips (S3, C6, C61, C5, ESP32) | beta — the C3 Supermini is the reference board |
| Setup opening itself on a USB cable | beta |
| The browser flasher | stable — installs the right image for the detected chip |
| GoPro (HERO9 and newer) | beta. Written to GoPro's published spec and checked byte for byte against GoPro's own SDK — the UUIDs, the packet framing, the status ids, the keep-alive, both status-id dialects |
| Osmo Action | beta — it speaks the same protocol as the Osmo 360 |
Beta means it is built to be right and we want to hear how it behaves on your aircraft, good or bad — the serial log tells the whole story, so attach it.
Found a problem? Open an issue.
I'm not taking pull requests. The licence does not grant the right to make derivative works, so a PR sits outside what it permits — and accepting one would leave the author holding copyright on code I could not then license under the same terms. Sorting that out means a contributor agreement, which is more process than this project wants.
That is a licensing decision and nothing to do with the quality of anyone's work. Please don't spend a weekend on a patch here; I would rather say so now than after.
Bug reports and questions are very welcome. If something is broken, or the OSD says one thing while your camera does another, open an issue — a good bug report is worth considerably more to this project than a patch I cannot merge.
