Skip to content

Commit d49bf2c

Browse files
knaufinatorclaude
andauthored
feat(firmware): Mini firmware as mini/ variant on the root stewart-core (#30)
* feat(firmware): bring Mini firmware in as the `mini/` variant on the root stewart-core Fold the standalone Mini-6DOF firmware into this repo as a second build-time firmware variant alongside `Controller/`, both sharing the single root `stewart-core` submodule. Source files copied in (not a subtree/submodule of Mini-6DOF). Part of the firmware-unification effort (6dof/FIRMWARE_UNIFICATION.md, DECISIONS 2026-07-18). Variant map - `Controller/` = normal (full system): MCPWM step/dir -> AASD-15A; PCBv1 ESP32 / PCBv2 ESP32-S3; W5500 Ethernet. - `mini/` = mini (desktop): LEDC PWM -> hobby servos; analog 50 Hz, runtime-switchable to digital 250 Hz via SERVO:RATE. - Both point at ../stewart-core (single source of truth = atan2f). Changes - Copy Mini-6DOF `Controller/` tree into `mini/` (main/, include/, CMakeLists.txt, partitions.csv, sdkconfig.defaults, embedded demo .m6p). Excludes .git, build/, generated sdkconfig, and the mini's own components/stewart-core submodule. - Rewire `mini/CMakeLists.txt` to the root submodule via `set(EXTRA_COMPONENT_DIRS ".../stewart-core")`, mirroring Controller/. The mini's `main/CMakeLists.txt` already PRIV_REQUIRES stewart-core; it now resolves to the root component. No `components/stewart-core` reference remains. - Advance the root `stewart-core` submodule e5ef989 -> a820e74 (origin/main head: "feat(mca): add output-stage intensity + per-axis gain/invert, schema v6"). Required: the mini Phase-3 firmware calls mcaGetIntensity / mcaSetAxisGain / mcaApplyOutputStage and MotionCueingConfig.axis_gain/axis_invert, which the old pin predates. Per the locked decision these cue params live in the SHARED core. - Copy mini 3D parts into `cad/mini/` (servo mount, seat rails, pcb mount). - Add `firmware/README.md` (variant map + build) and rewrite `mini/README.md` (variant framing, hardware/pinout/geometry, servo-rate profile, commands). Both document the config-follows-flashed-features direction (FINGERPRINT advertises caps; app/bridge derive config) and the deferred Controller/->normal/ + firmware/common/ refactor. Build-verified on ESP-IDF v5.5 (both reach "Project build complete" with fresh bins): - mini -> esp32 -> mini_6dof.bin - Controller -> esp32s3 -> stewart_platform.bin (unchanged by the submodule bump) Not done here (deliberately): Mini-6DOF not archived; no hardware flashed; no handshake re-architecture; no Controller/->normal/ + firmware/common/ move. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(firmware): move variant map to root FIRMWARE.md; add mini CI job + close trigger gaps Address coordinator review on the firmware-unification PR: - Move firmware/README.md -> repo-root FIRMWARE.md (variant map lives next to mini/ and Controller/); remove the lonely firmware/ directory. Fix all relative links (../ prefixes) now that it sits at the root. - Drop mini/build-esp-idf.ps1 (redundant with the 6dof-esp-build skill); mini/README.md now points at the skill for headless builds. - CI (.github/workflows/ci.yml): add a "Build Mini Firmware (ESP-IDF)" job that builds mini/ for esp32 (mirrors the ESP32-S3 Controller job: recursive submodules, esp-idf-ci-action v5.5.2, uploads mini_6dof.* artifacts). - Broaden path triggers so a shared-core bump can't slip past CI: * ci.yml (firmware + SIL): add mini/**, stewart-core (submodule gitlink), and the workflow file itself. * unit_tests.yml (C math tests): add stewart-core — the math under test now lives in the submodule, so a bump must retrigger the tests. This is the exact gap that let the e5ef989->a820e74 bump go unverified. Re-verified: mini esp32 build still reaches "Project build complete" with a fresh mini_6dof.bin. App/SIL tests confirmed 47/47 against a820e74 by the coordinator. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fb1685e commit d49bf2c

24 files changed

Lines changed: 2597 additions & 1 deletion

.github/workflows/ci.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,18 @@ on:
77
- 'v*.*.*'
88
paths:
99
- 'Controller/**'
10+
- 'mini/**'
1011
- 'app/**'
12+
- 'stewart-core' # shared submodule gitlink — a bump must trigger firmware + SIL
13+
- '.github/workflows/ci.yml'
1114
pull_request:
1215
branches: [ phoenix, main ]
1316
paths:
1417
- 'Controller/**'
18+
- 'mini/**'
1519
- 'app/**'
20+
- 'stewart-core' # shared submodule gitlink — a bump must trigger firmware + SIL
21+
- '.github/workflows/ci.yml'
1622

1723
jobs:
1824
version:
@@ -115,6 +121,50 @@ jobs:
115121
partition-table-*.bin
116122
retention-days: 90
117123

124+
mini-firmware-build:
125+
name: Build Mini Firmware (ESP-IDF)
126+
needs: version
127+
runs-on: ubuntu-latest
128+
129+
steps:
130+
- name: Checkout repository
131+
uses: actions/checkout@v4
132+
with:
133+
submodules: 'recursive' # root stewart-core supplies the shared math
134+
135+
- name: Install ESP-IDF prerequisites
136+
run: |
137+
sudo apt-get update
138+
sudo apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv \
139+
cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
140+
141+
- name: Set up ESP-IDF and Build
142+
uses: espressif/esp-idf-ci-action@v1
143+
with:
144+
esp_idf_version: v5.5.2
145+
target: esp32
146+
path: 'mini'
147+
command: idf.py build
148+
149+
- name: Package mini firmware with version
150+
run: |
151+
VERSION="${{ needs.version.outputs.version }}"
152+
cp mini/build/mini_6dof.bin "mini-firmware-${VERSION}.bin"
153+
cp mini/build/mini_6dof.elf "mini-firmware-${VERSION}.elf"
154+
cp mini/build/bootloader/bootloader.bin "mini-bootloader-${VERSION}.bin"
155+
cp mini/build/partition_table/partition-table.bin "mini-partition-table-${VERSION}.bin"
156+
157+
- name: Upload mini firmware artifacts
158+
uses: actions/upload-artifact@v4
159+
with:
160+
name: mini-firmware-${{ needs.version.outputs.version }}
161+
path: |
162+
mini-firmware-*.bin
163+
mini-firmware-*.elf
164+
mini-bootloader-*.bin
165+
mini-partition-table-*.bin
166+
retention-days: 90
167+
118168
desktop-app-build:
119169
name: Build Desktop App (CMake)
120170
needs: version

.github/workflows/unit_tests.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
- 'Controller/main/AxisScaling.cpp'
99
- 'Controller/main/MotionCueing.cpp'
1010
- 'Controller/include/**'
11+
- 'stewart-core' # shared submodule gitlink — the math under test lives here now
1112
- 'tests/**'
1213
- '.github/workflows/unit_tests.yml'
1314
pull_request:
@@ -17,6 +18,7 @@ on:
1718
- 'Controller/main/AxisScaling.cpp'
1819
- 'Controller/main/MotionCueing.cpp'
1920
- 'Controller/include/**'
21+
- 'stewart-core' # shared submodule gitlink — the math under test lives here now
2022
- 'tests/**'
2123
- '.github/workflows/unit_tests.yml'
2224

FIRMWARE.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Firmware variants
2+
3+
This repo carries **one firmware codebase in two build-time variants**, both sharing the single
4+
[`stewart-core`](stewart-core) submodule at the repo root (IK / AxisScaling / MotionCueing —
5+
the single source of truth, canonical `atan2f`). The variant is selected at build time via the
6+
IDF target + the actuator backend; the shared motion pipeline (COBS transport, playback, CueTask,
7+
command handler, NVS config) is identical.
8+
9+
| Variant | Directory | Actuator backend | Boards / MCU | Servo rate | Build |
10+
|---------|-----------|------------------|--------------|-----------|-------|
11+
| **normal** (full system) | [`Controller/`](Controller) | MCPWM step/dir → AASD-15A AC servos (MCP23S17 SPI expander on PCBv1; direct GPIO on PCBv2). W5500 Ethernet. | PCBv1 = ESP32, PCBv2 = ESP32-S3 | 250 Hz cue loop | `set-target esp32`\|`esp32s3` |
12+
| **mini** (desktop) | [`mini/`](mini) | LEDC PWM → hobby servos | ESP32 DevKitC | analog 50 Hz (runtime-switchable to digital 250 Hz via `SERVO:RATE`) | `set-target esp32` |
13+
14+
Both directories are project-level ESP-IDF projects that point at the root submodule with
15+
`set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../stewart-core")` and resolve it through
16+
`PRIV_REQUIRES stewart-core` in their `main/CMakeLists.txt`. There is **no per-variant copy** of
17+
the core math and **no `components/stewart-core` submodule** inside either variant.
18+
19+
```
20+
<repo root>/
21+
├── Controller/ normal (full-system) firmware variant → ../stewart-core
22+
├── mini/ mini (desktop) firmware variant → ../stewart-core
23+
├── stewart-core single shared submodule (IK/AxisScaling/MotionCueing)
24+
├── cad/mini/ mini 3D-printed parts (servo mounts, seat rails)
25+
├── app/ desktop SIL/HIL simulator (also consumes stewart-core)
26+
├── bridge/ Linux HIL bridge service
27+
├── pcb/ tests/ docs/
28+
```
29+
30+
## Building
31+
32+
Requires ESP-IDF v5.5. Fetch the shared core first:
33+
34+
```bash
35+
git submodule update --init --recursive
36+
```
37+
38+
```bash
39+
# mini variant
40+
cd mini && idf.py set-target esp32 && idf.py build
41+
42+
# normal variant — PCBv2 (ESP32-S3)
43+
cd Controller && idf.py set-target esp32s3 && idf.py build
44+
# normal variant — PCBv1 (ESP32)
45+
cd Controller && idf.py set-target esp32 && idf.py build
46+
```
47+
48+
Per-variant hardware notes, pinouts, and serial commands: [`mini/README.md`](mini/README.md)
49+
and [`Controller/README.md`](Controller/README.md).
50+
51+
On this machine, both variants build headlessly via the **`6dof-esp-build`** skill
52+
(`idf_build.ps1`), which encodes the ESP-IDF env setup.
53+
54+
## Config-follows-flashed-features (direction)
55+
56+
The intended architecture: **nothing about a device's capabilities is hardcoded per connection.**
57+
Build-time feature flags determine the flashed feature set; the device then **advertises what it is**
58+
via the FINGERPRINT handshake (variant, PCB version, actuator type, servo mode, axis count,
59+
`caps=raw`, partition/OTA presence, …); the app / Linux bridge / Android client **derive** the
60+
UI / config / param-set from that report. FINGERPRINT already reports `platform=…` and `caps=raw`;
61+
extending it to the full capability set is the follow-up. **The handshake is not being
62+
re-architected now** — this section only records the direction.
63+
64+
## Planned follow-up refactor (NOT done here)
65+
66+
The current layout adds `mini/` as a sibling of the existing `Controller/`, the lowest-risk first
67+
step. The intended end state groups by variation and factors the shared firmware out:
68+
69+
```
70+
firmware/
71+
├── common/ transport, playback, CueTask, motion pipeline, command handler, config
72+
└── variants/
73+
├── normal/ (today's Controller/)
74+
└── mini/ (today's mini/)
75+
```
76+
77+
Deferred deliberately: moving `Controller/``normal/` and extracting `firmware/common/` is
78+
disruptive to the app's CMake wiring and CI, so it is a separate follow-up once both variants are
79+
proven in-tree. Do not fold it into this change.

cad/mini/Lower Seat Rail.stl

35.2 KB
Binary file not shown.

cad/mini/Seat Rail.stl

286 KB
Binary file not shown.

cad/mini/Servo Motor Mount.stl

404 KB
Binary file not shown.

cad/mini/pcb mount v1.stl

197 KB
Binary file not shown.

mini/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# ESP-IDF CMakeLists.txt for Mini-6DOF Controller
2+
# PWM servo variant of the Stewart Platform controller
3+
cmake_minimum_required(VERSION 3.16)
4+
5+
# Support both ESP32 and ESP32-S3
6+
# set(SUPPORTED_TARGETS esp32 esp32s3)
7+
8+
# Shared IK / AxisScaling / MotionCueing come from the SINGLE stewart-core submodule
9+
# at the repo root (single source of truth = atan2f), consumed as an out-of-tree
10+
# component — mirrors Controller/CMakeLists.txt. The mini/ variant does NOT carry its
11+
# own components/stewart-core; the root submodule provides the `stewart-core` component
12+
# that main/CMakeLists.txt PRIV_REQUIRES.
13+
set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../stewart-core")
14+
15+
# Include ESP-IDF build system
16+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
17+
18+
# Define the project
19+
project(mini_6dof)

mini/README.md

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# `mini/` — Mini-6DOF Firmware Variant
2+
3+
Desktop-scale **mini** variant of the Stewart-platform firmware: an **ESP32** driving
4+
**6 hobby servos via LEDC PWM**. This is one of two firmware variants in this repo — see
5+
[`../FIRMWARE.md`](../FIRMWARE.md) for the variant map. Both variants share the
6+
single [`stewart-core`](../stewart-core) submodule at the repo root (IK / AxisScaling /
7+
MotionCueing — single source of truth).
8+
9+
| | This variant (`mini/`) | `Controller/` (normal) |
10+
|---|---|---|
11+
| **Role** | desktop / dev / education | full-scale motion sim |
12+
| **MCU** | ESP32 (DevKitC) | ESP32 (PCBv1) / ESP32-S3 (PCBv2) |
13+
| **Actuator** | LEDC PWM → hobby servos | MCPWM step/dir → AASD-15A AC servos |
14+
| **Servo rate** | analog 50 Hz (runtime-switchable to digital 250 Hz via `SERVO:RATE`) | 250 Hz cue loop |
15+
| **Wireless** | BLE (Android phone-as-controller) | W5500 Ethernet / WiFi |
16+
| **Workspace** | ~30 mm | ~350 mm |
17+
18+
> This tree was migrated from the standalone **Mini-6DOF** repo (its `Controller/`, Phase-3
19+
> firmware) into the main app repo as part of the firmware-unification effort. The mini's former
20+
> `components/stewart-core` submodule was dropped in favor of the root submodule.
21+
22+
## Build
23+
24+
Requires ESP-IDF v5.5 (matches the rest of the repo).
25+
26+
```bash
27+
# from repo root, one-time: fetch the shared core
28+
git submodule update --init --recursive
29+
30+
cd mini
31+
idf.py set-target esp32 # first time only
32+
idf.py build
33+
idf.py -p COM6 flash monitor
34+
```
35+
36+
`mini/CMakeLists.txt` points at the root submodule via
37+
`set(EXTRA_COMPONENT_DIRS "${CMAKE_CURRENT_LIST_DIR}/../stewart-core")` (mirrors
38+
`Controller/CMakeLists.txt`); `main/CMakeLists.txt` resolves it through `PRIV_REQUIRES stewart-core`.
39+
40+
On this machine, the headless build is driven by the **`6dof-esp-build`** skill
41+
(`idf_build.ps1 -ProjDir mini -Clean`), which encodes the ESP-IDF v5.5 env setup that avoids the
42+
MSYS/venv/stderr traps. CI builds it the same way (see the "Build Mini Firmware" job in
43+
`.github/workflows/ci.yml`).
44+
45+
## Project structure
46+
47+
```
48+
mini/
49+
├── main/
50+
│ ├── main.cpp # app_main, CueTask, SOURCE modes, command API, LEDC servo output
51+
│ ├── CobsTransport.cpp # COBS-framed serial transport (CH_DATA18 / CH_DATA_RAW / CH_CMD)
52+
│ ├── BleTransport.cpp # BLE GATT server (motion + accel characteristics)
53+
│ ├── helpers.cpp # mapfloat / rate-limit utilities
54+
│ ├── laps123_moderate.m6p # baked 50 Hz motion-cued laps 1-3 (EMBED_FILES) for boot demo
55+
│ └── CMakeLists.txt # component build (PRIV_REQUIRES stewart-core)
56+
├── include/ # BleTransport.h, CobsTransport.h, cobs.h, helpers.h, version.h, debug_uart.h
57+
├── partitions.csv # 4 MB no-OTA table: 2 MB app + raw seq partition
58+
├── sdkconfig.defaults # ESP32 config
59+
└── CMakeLists.txt # project-level, wires EXTRA_COMPONENT_DIRS → ../stewart-core
60+
```
61+
62+
IK / AxisScaling / MotionCueing are **not** in this tree — they come from `../stewart-core`.
63+
The only mini-specific code is the LEDC servo backend + BLE transport + pin map.
64+
65+
## Hardware
66+
67+
- **MCU**: ESP32 DevKitC (original ESP32 — not ESP32-S3)
68+
- **Servos**: 6 × hobby servos (SG90/MG996R class; ordering Feetech/ANNIMOS DS3218 for the digital upgrade)
69+
- **PWM**: LEDC at 50 Hz (analog default), 16-bit resolution
70+
- **Pulse range**: 800–2200 µs (center 1500 µs)
71+
- **Power**: dedicated **5 V 8 A** servo rail (do not run servos off USB)
72+
- **Servo enable**: GPIO 27 controls the servo-rail power relay/MOSFET
73+
74+
### Servo pinout
75+
76+
| Servo | GPIO | LEDC ch | Inverted |
77+
|-------|------|---------|----------|
78+
| 0 | 15 | 0 | Yes |
79+
| 1 | 14 | 1 | No |
80+
| 2 | 4 | 2 | Yes |
81+
| 3 | 32 | 3 | No |
82+
| 4 | 33 | 4 | Yes |
83+
| 5 | 5 | 5 | No |
84+
85+
| Function | GPIO |
86+
|----------|------|
87+
| Servo enable | 27 |
88+
| E-Stop | 22 |
89+
90+
Inverted servos are mounted mirrored; the firmware applies the sign flip automatically.
91+
92+
### Platform geometry (mm)
93+
94+
| Param | Value | Description |
95+
|-------|-------|-------------|
96+
| RD | 15.75 | Base radius |
97+
| PD | 16.00 | Platform radius |
98+
| L1 | 7.25 | Servo horn length |
99+
| L2 | 28.50 | Connecting rod length |
100+
| H | 25.517 | Neutral platform height |
101+
| θ_r | 10° | Base rotation angle |
102+
| θ_p | 30° | Platform rotation angle |
103+
104+
3D-printed mounts + seat rails are in [`../cad/mini/`](../cad/mini).
105+
106+
## Servo rate profile (analog / digital)
107+
108+
The firmware supports **both** an analog 50 Hz carrier (SG90/MG996R class — default) and a digital
109+
250 Hz carrier (DS3218 class), runtime-switchable via `SERVO:RATE` (persisted in NVS). `SERVO:RATE`
110+
sets the LEDC carrier and `cueLoopHz` together, so a servo swap needs no reflash. Staying on analog
111+
50 Hz now; the digital-servo swap later flips the profile to 250 Hz and unlocks the high-fidelity
112+
mini cue loop. See `6dof/MINI_SERVO_UPGRADE.md` for the servo spec.
113+
114+
## Communication
115+
116+
- **Serial**: COBS-framed over USB — `CH_DATA18` (0x06, baked 18-byte motion), `CH_DATA_RAW`
117+
(0x07, raw 6×float32 live telemetry), `CH_CMD` (0x02, ASCII commands). Legacy 15-byte
118+
`0xAA/0x55` binary + CSV also accepted.
119+
- **BLE**: GATT server (device name `Mini6DOF`) — Motion char `0xFF01` (12 B, 6×uint16 LE),
120+
Accel char `0xFF03` (24 B, 6×float32 LE) for phone-as-controller.
121+
122+
### Selected serial commands
123+
124+
| Command | Description |
125+
|---------|-------------|
126+
| `VERSION?` / `FINGERPRINT?` | Version + identity handshake (reports `platform=mini-6dof`, `caps=raw`) |
127+
| `CONFIG?` / `CONFIG:key=value` | Geometry dump / set (auto-recomputes axis scales) |
128+
| `SCALE?` / `BITS?` / `BITS:N` | Axis scales / input bit depth |
129+
| `SERVO:RATE=...` | Analog 50 Hz ↔ digital 250 Hz profile (NVS-persisted) |
130+
| `SERVO:CENTER=c0..c5` / `SERVO:PULSE=v` | Servo center + pulse-per-radian calibration |
131+
| `SOURCE:OFF\|DEMO\|LIVE` | Motion source select (`OFF` homes = one-tap kill) |
132+
| `PLAY:BOOT=0\|1` | Disable/enable boot auto-play (runtime, no reflash) |
133+
| `ZERO` / `ESTOP:SOFT` / `DBG:1`\|`DBG:0` | Home / soft e-stop / debug toggle |
134+
135+
Runtime serial control without reflashing: see the `6dof-mini-serial` skill.
136+
137+
## Config-follows-flashed-features (direction)
138+
139+
The intended architecture is that **nothing about a device's capabilities is hardcoded per
140+
connection** — the device advertises what it is (variant, PCB version, actuator type, servo mode,
141+
axis count, `caps=raw`, partition/OTA presence) via the **FINGERPRINT handshake**, and the app /
142+
Linux bridge / Android client **derive** their UI/config/param-set from that report. Build-time
143+
feature flags determine the flashed feature set → the firmware reports it at runtime → the config
144+
follows. FINGERPRINT already reports `platform=mini-6dof` and `caps=raw`; extending it to the full
145+
capability set is the follow-up (the handshake is **not** being re-architected now).
146+
147+
## License
148+
149+
MIT — see [`../LICENSE`](../LICENSE).

mini/include/BleTransport.h

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#ifndef BLE_TRANSPORT_H
2+
#define BLE_TRANSPORT_H
3+
4+
#ifdef ENABLE_BLE
5+
6+
#include <stdbool.h>
7+
#include <stdint.h>
8+
9+
#ifdef __cplusplus
10+
extern "C" {
11+
#endif
12+
13+
/**
14+
* Initialize BLE GATT server with a custom motion data service.
15+
*
16+
* Service UUID: 4210xxxx-0001-1000-8000-00805f9b34fb
17+
* Motion RX characteristic (0xFF01): writable, receives 12-byte binary payloads
18+
* Status TX characteristic (0xFF02): notify, sends status/telemetry
19+
* Accel RX characteristic (0xFF03): writable, receives 24-byte accel packets
20+
*
21+
* @param process_packet Callback for each valid 12-byte binary payload
22+
* @return true on success
23+
*/
24+
bool ble_transport_init(void (*process_packet)(const uint8_t *payload));
25+
26+
/**
27+
* Register callback for accelerometer/gyro data packets.
28+
* Called when 24 bytes (6 x float32 LE) arrive on the accel characteristic.
29+
* Data order: [accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z]
30+
* Units: m/s² for accel, rad/s for gyro (Android TYPE_ACCELEROMETER + TYPE_GYROSCOPE)
31+
*
32+
* @param process_accel Callback receiving pointer to 6 floats
33+
*/
34+
void ble_transport_set_accel_callback(void (*process_accel)(const float *data));
35+
36+
/**
37+
* Returns true when a BLE client is connected.
38+
*/
39+
bool ble_transport_connected(void);
40+
41+
/**
42+
* Get BLE connection state as string.
43+
*/
44+
const char* ble_transport_state_str(void);
45+
46+
/**
47+
* Send notification data to connected BLE client (for telemetry).
48+
* @param data Pointer to data bytes
49+
* @param len Length of data
50+
* @return 0 on success
51+
*/
52+
int ble_transport_notify(const uint8_t *data, uint16_t len);
53+
54+
#ifdef __cplusplus
55+
}
56+
#endif
57+
58+
#endif // ENABLE_BLE
59+
#endif // BLE_TRANSPORT_H

0 commit comments

Comments
 (0)