Skip to content

feat: ESP32-C3 Super Mini target, GoPro link fix, DJI Action 2 support - #2

Open
miszczuq wants to merge 1 commit into
YLabs-FPV:mainfrom
Modulo-MM:feature/esp32c3-supermini-action2
Open

miszczuq wants to merge 1 commit into
YLabs-FPV:mainfrom
Modulo-MM:feature/esp32c3-supermini-action2

Conversation

@miszczuq

Copy link
Copy Markdown

Add a second board target and a new camera backend, and fix the GoPro link being dropped right after it connects.

ESP32-C3 Super Mini (new esp32c3-supermini env)

  • platformio.ini: shared settings moved to [env]; new env on the nologo_esp32c3_super_mini board (ESP32-C3FH4: 4 MB flash, no PSRAM, USB-Serial/JTAG). The esp32s3-zero env builds as before.
  • config.h: per-board pin maps selected by a BOARD_* define. C3: FC UART on GPIO20 (RX) / GPIO21 (TX), status LED on GPIO8.
  • StatusLed: support a plain single-colour LED (the C3 board has a blue active-low LED instead of a WS2812); states are told apart by blink pattern: slow blink = searching, double flash = config Wi-Fi client, solid = camera connected, fast blink = recording.
  • main.cpp: on single-core chips loop() blocks for a tick so the camera task and the idle task are not starved.
  • README: build, flash and wiring notes for the C3 board.

GoPro

  • Fix the link being dropped as "stale" right after connecting: the check used a timestamp taken before the blocking status/setting writes, so a reply landing during them made now - lastUpdateMs underflow.
  • Log the NimBLE error code when a connect attempt fails.

DJI Action 2 (new "DJI Action 2" camera type)

  • New DjiAction2Camera backend: DUML over BLE with the app-level pairing the Mimo app uses (01 00 arm write to FFF4, 0x07/0x45 pairing request, ACK of the on-screen approval), record start/stop via 0x02/0x02. Pairing flow ported from shutterlink (MIT).
  • The camera sends no telemetry once paired, so the BLE link itself is the liveness signal and record state/time come from our own commands. Photo capture, mode switch, SD free, time left and resolution are unavailable.
  • Web UI: new camera type in the bind wizard; every DJI camera is listed for each DJI type (the scan can't tell an Action 2 apart); OSD fields and channel functions the Action 2 can't provide are hidden.

Tested on an ESP32-C3 Super Mini:

  • flashes and boots, Wi-Fi AP and Web UI work
  • GoPro HERO12 pairs and stays connected with live status
  • DJI Action 2 pairs; link stability after the liveness change and record start/stop not verified yet
  • not yet tested with a flight controller (MSP, OSD, record on arm)

Add a second board target and a new camera backend, and fix the GoPro
link being dropped right after it connects.

ESP32-C3 Super Mini (new `esp32c3-supermini` env)
- platformio.ini: shared settings moved to [env]; new env on the
  nologo_esp32c3_super_mini board (ESP32-C3FH4: 4 MB flash, no PSRAM,
  USB-Serial/JTAG). The esp32s3-zero env builds as before.
- config.h: per-board pin maps selected by a BOARD_* define. C3: FC UART
  on GPIO20 (RX) / GPIO21 (TX), status LED on GPIO8.
- StatusLed: support a plain single-colour LED (the C3 board has a blue
  active-low LED instead of a WS2812); states are told apart by blink
  pattern: slow blink = searching, double flash = config Wi-Fi client,
  solid = camera connected, fast blink = recording.
- main.cpp: on single-core chips loop() blocks for a tick so the camera
  task and the idle task are not starved.
- README: build, flash and wiring notes for the C3 board.

GoPro
- Fix the link being dropped as "stale" right after connecting: the check
  used a timestamp taken before the blocking status/setting writes, so a
  reply landing during them made `now - lastUpdateMs` underflow.
- Log the NimBLE error code when a connect attempt fails.

DJI Action 2 (new "DJI Action 2" camera type)
- New DjiAction2Camera backend: DUML over BLE with the app-level pairing
  the Mimo app uses (01 00 arm write to FFF4, 0x07/0x45 pairing request,
  ACK of the on-screen approval), record start/stop via 0x02/0x02.
  Pairing flow ported from shutterlink (MIT).
- The camera sends no telemetry once paired, so the BLE link itself is the
  liveness signal and record state/time come from our own commands. Photo
  capture, mode switch, SD free, time left and resolution are unavailable.
- Web UI: new camera type in the bind wizard; every DJI camera is listed
  for each DJI type (the scan can't tell an Action 2 apart); OSD fields and
  channel functions the Action 2 can't provide are hidden.

Tested on an ESP32-C3 Super Mini:
- flashes and boots, Wi-Fi AP and Web UI work
- GoPro HERO12 pairs and stays connected with live status
- DJI Action 2 pairs; link stability after the liveness change and record
  start/stop not verified yet
- not yet tested with a flight controller (MSP, OSD, record on arm)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@YarosMallorca YarosMallorca added bug Something isn't working enhancement New feature or request camera labels Sep 24, 2026
@YarosMallorca
YarosMallorca self-requested a review September 24, 2026 10:31
@YarosMallorca YarosMallorca linked an issue Sep 24, 2026 that may be closed by this pull request
9 tasks

@YarosMallorca YarosMallorca left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great overall, thanks! I added a few points to look into, and verify on a real Action 2 if you own one (I don't), as most code was copied from shutterlink as the source of truth.

I will receive my ESP-C3 next week and test on it, to confirm that everything works. I will try to get access to a GoPro as well; in the meantime, if you could look further into the Action 2, especially on the points I've mentioned below, I'd appreciate it!

Comment on lines +213 to +218
if (start && !status_.isRecording()) {
recStartMs_ = millis();
status_.recElapsedS = 0;
}
status_.recState = start ? RecState::Recording : RecState::Idle;
return true;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recordCtrl sets status_.recState optimistically and returns true.
If the camera NAKs the command (handleFrame handles 0x02/0x02 ACK with p[0] != 0x00, e.g. 0xd9 wrong-state), it only logs; it never reverts recState.

The OSD then shows REC while the camera isn't recording. Please snapshot the prior recState/recElapsedS before the optimistic write and restore them on a non-zero reply.

Comment on lines +161 to +170
// Once paired the camera goes quiet - no status pushes, no reply to the keep-alive -
// so the BLE link itself (supervision timeout -> onDisconnect) is the liveness
// signal, and the record time runs from our own accepted start
status_.lastUpdateMs = t;
if (status_.isRecording())
status_.recElapsedS = (t - recStartMs_) / 1000;
if (t - lastKeepMs_ >= kKeepAliveMs) {
lastKeepMs_ = t;
keepAlive();
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Camera goes quiet after pairing" is likely incorrect, likely copied from shutterlink without questioning.

The design tracks its own elapsed time and relies purely on the BLE supervision timeout for liveness, on the premise that "the camera goes quiet - no status pushes, no reply to the keep-alive." Independent reverse engineering suggests otherwise for the Osmo DUML family:

  • Osmosis MEDIA_PROTOCOL.md: 0x02/0x80 Camera Status is pushed ~10 Hz (recording bit at byte 0, 0x01→0x81); 0x0D/0x02 Battery ~1 Hz; 0x02/0xDC Storage push. "Status pushes arrive automatically; periodic polling not required."
  • The dedicated FLORIANSV35/controle-dji-action2 project reports real C REC/C STP states, implying it parses returned state.
  • DjiOsmoCamera already parses 0x02/0x80 for the Nano.

If the Action 2 does push these, this backend could report true recording state/elapsed, battery, and SD (parity with the other backends) and use a real telemetry-stale drop.
This backend skips the init/subscribe burst that DjiOsmoCamera sends, which may be what actually enables the pushes.

Since I don't have an Action 2, can't verify. If you do own one, please verify and report back.

Comment on lines +260 to +265
if (f.cmdSet == 0x02 && f.cmdId == 0x02 && f.flags == duml::FLAG_ACK && n >= 1) {
if (p[0] == 0x00)
status_.recState = lastCmdStart_ ? RecState::Recording : RecState::Idle;
else
Serial.printf("[a2] record command rejected (0x%02x)\n", p[0]);
return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same applies here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

board bug Something isn't working camera enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GoPro Hero 12 Black - drops connection constantly

2 participants