Convert to external .fap; export ST BLE command API from the firmware

build.sh now adds ST's aci_*/hci_* headers to the SDK (with extern "C"
guards, the API table is C++), lets fbt regenerate api_symbols.csv and
enables the selected functions (BLE_API=hci,gatt default; min; all).
The app builds as a normal .fap shipped inside the update package and
loadable from SD, saving ~12 KB of firmware flash versus the built-in
(min mode: ~22 KB). qsort replaced with an insertion sort since libc's
qsort is not exported to apps. README documents modes and flash budget.
This commit is contained in:
2026-09-11 11:56:17 +01:00
parent e863616f82
commit 999b20f378
6 changed files with 201 additions and 36 deletions
+51 -17
View File
@@ -9,7 +9,8 @@ services and characteristics: read, write, subscribe, and log it all.
> Flipper's radio coprocessor ships with ST's "BLE Light" stack, which can only
> advertise (peripheral role). It cannot scan for or connect to other devices.
> BT Inspector is therefore delivered as a **custom firmware build** that swaps
> in ST's full BLE stack and compiles the app in. `build.sh` does all of this.
> in ST's full BLE stack and exports the BLE command API to apps. The app itself
> is a normal `.fap`. `build.sh` does all of this.
> See [Why a custom firmware build](#why-a-custom-firmware-build).
## Features
@@ -62,19 +63,34 @@ Requirements: Linux or macOS with `git`, `python3` and a few GB of disk. The
firmware build system downloads its own ARM toolchain.
```sh
./build.sh # clone firmware 1.4.3, add the app, build with the full BLE stack
./build.sh # clone firmware 1.4.3, export the BLE API, build firmware + .fap
./build.sh flash # same, then flash over USB (Flipper connected, qFlipper closed)
./build.sh fap # rebuild only the .fap after editing the app
```
Options via environment variables: `FW_TAG` (firmware tag, default `1.4.3`),
`FW_DIR` (where to clone), `JOBS` (parallel compile jobs, default 4).
`FW_DIR` (where to clone), `JOBS` (parallel compile jobs, default 4),
`BLE_API` (which ST API to export, see below).
The result is a standard update package in
`flipperzero-firmware/dist/f7-C/f7-update-local/`. To install without USB
flashing, copy that folder to the SD card (for example `SD:/update/`) and open
it in the Flipper's file browser: the updater installs the firmware **and the
full radio stack**. Installing an official firmware later restores the light
stack the same way.
`flipperzero-firmware/dist/f7-C/f7-update-local/`. It contains the firmware,
the **full radio stack**, and the app as `apps/Bluetooth/bt_inspector.fap` in
its resources. To install without USB flashing, copy that folder to the SD card
(for example `SD:/update/`) and open it in the Flipper's file browser.
Installing an official firmware later restores the light stack the same way.
The `.fap` alone is at `flipperzero-firmware/build/f7-firmware-C/.extapps/`;
after the firmware is installed once, app updates are just a file copy to
`SD:/apps/Bluetooth/`. It only loads on this firmware (API version 87.2 with
the exported symbols); the stock loader rejects it with an API mismatch.
To build the app with `ufbt` instead of the firmware tree, point `ufbt` at the
SDK the build produces:
```sh
ufbt update --local=flipperzero-firmware/dist/f7-C/flipper-z-f7-sdk-local.zip
cd bt_inspector && ufbt # -> dist/bt_inspector.fap ; `ufbt launch` installs it
```
The first build takes 5-15 minutes; later builds are incremental.
@@ -128,12 +144,26 @@ Three facts about the Flipper Zero drive the design:
firmware build system already supports it (`COPRO_STACK_TYPE=ble_full`) and
the firmware recognises it at runtime (`FuriHalBtStackFull`).
2. External `.fap` apps can only call functions listed in the firmware's API
table. The ST `aci_*` / `hci_*` command functions are not in it, so a `.fap`
cannot start a scan or a connection. Compiling the app into the firmware
(`FlipperAppType.APP`, added with `--extra-int-apps`) gives it access.
3. The full stack is 34 KB larger and loads at 0x080CE000 instead of
0x080D7000. The built image still leaves 12 flash pages (52 KB) for the
Flipper's internal storage (stock leaves 27).
table. The ST `aci_*` / `hci_*` command functions are not in it, so a stock
`.fap` cannot start a scan or a connection. `build.sh` adds ST's command
headers to the SDK (`lib/stm32wb.scons`), adds `extern "C"` guards to them
(the API table is C++), lets `fbt` regenerate `api_symbols.csv`, and enables
the selected functions. The API minor version becomes 87.2.
3. Every exported function is kept in the firmware image, so `BLE_API` trades
generality for flash. The full stack loads at 0x080CE000 (light: 0x080D7000);
the space between the firmware and the stack is the Flipper's internal
storage:
| `BLE_API` | exported | firmware | pages left, full stack | pages left, extended stack |
|---|---|---|---|---|
| `min` (app's own 15 imports) | 15 | 770 KB | 18 | 9 |
| `hci,gatt` (default) | 127 | 779 KB | 15 | 6 |
| `all` | 211 | ~788 KB | 13 | 4 |
| stock light-stack firmware | 0 | 768 KB | 27 | n/a |
Any comma list of `hci,gap,gatt,hal,l2cap` works. `extended` refers to
`stm32wb5x_BLE_Stack_full_extended_fw.bin` (BLE 5 extended advertising,
Coded PHY), which loads at 0x080C5000; it is not selected by this script.
`build.sh` also widens the GAP roles the firmware initialises
(`GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE | GAP_OBSERVER_ROLE`) in
@@ -182,8 +212,12 @@ wire overlay).
Install the package produced by `build.sh`, which includes `radio.bin` with
the full stack.
* **Build killed / out of memory**: `JOBS=2 ./build.sh`.
* **Updater link errors about `bt`/`rpc`**: the app manifest must not `require`
the `bt` service (the same app list is linked into the tiny updater image).
* **App refuses to load ("API mismatch" / unresolved symbol)**: the firmware on
the device is not the one from `build.sh`, or it was built with a smaller
`BLE_API` than the app needs. Reinstall the update package.
* **"API version is still WIP"** during a build: `fbt` found symbols the script
did not classify. Open `targets/f7/api_symbols.csv`, replace the remaining
`?` marks with `+` or `-`, rerun.
* **Connect failed: Timeout**: the device may not be connectable (see the
advertising type on the device page), or it is out of range. Random-address
devices that rotate their address may need re-selecting from the list.
@@ -191,7 +225,7 @@ wire overlay).
## Layout
```
bt_inspector/ app sources + application.fam
bt_inspector/ app sources, application.fam, icon.png
build.sh firmware build / flash script
flipperzero-firmware/ created by build.sh (git-ignored)
```