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.
167 lines
6.9 KiB
Bash
Executable File
167 lines
6.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build a Flipper Zero firmware update package that contains BT Inspector.
|
|
#
|
|
# Why a firmware build and not a plain .fap:
|
|
# * The stock radio coprocessor image is ST's "BLE Light" stack: peripheral/
|
|
# broadcaster only. Scanning and connecting as a central need the "full"
|
|
# stack (COPRO_STACK_TYPE=ble_full, already supported by fbt).
|
|
# * The firmware does not export the ST GAP/GATT/HCI command API to external
|
|
# apps. This script adds those headers to the SDK and accepts the new
|
|
# aci_*/hci_* symbols, so the app builds as a normal .fap (bumps the API
|
|
# minor version: the .fap only loads on this firmware).
|
|
#
|
|
# Usage:
|
|
# ./build.sh build -> flipperzero-firmware/dist/f7-C/f7-update-*/ (update package,
|
|
# includes the .fap under apps/Bluetooth) + SDK zip for ufbt
|
|
# ./build.sh flash build and flash over USB (Flipper connected, qFlipper closed)
|
|
# ./build.sh fap rebuild only the .fap (firmware already built once)
|
|
# BLE_API=all|min|hci,gap,gatt,hal,l2cap which ST API to export (default hci,gatt)
|
|
# FW_TAG=1.4.3 ./build.sh pin a different firmware tag (default 1.4.3)
|
|
# JOBS=2 ./build.sh limit parallel compile jobs (default 4)
|
|
set -euo pipefail
|
|
|
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
APP_SRC="$HERE/bt_inspector"
|
|
FW_DIR="${FW_DIR:-$HERE/flipperzero-firmware}"
|
|
FW_TAG="${FW_TAG:-1.4.3}"
|
|
FW_REPO="https://github.com/flipperdevices/flipperzero-firmware.git"
|
|
|
|
if [ ! -d "$FW_DIR/.git" ]; then
|
|
echo ">> cloning firmware $FW_TAG into $FW_DIR"
|
|
git clone --depth 1 --branch "$FW_TAG" --recurse-submodules --shallow-submodules "$FW_REPO" "$FW_DIR"
|
|
fi
|
|
|
|
cd "$FW_DIR"
|
|
|
|
# Let GAP take the central/observer roles as well as peripheral (harmless on the
|
|
# full stack; the app itself uses HCI-level scan/connect commands).
|
|
GAP_C=targets/f7/ble_glue/gap.c
|
|
if grep -q "^ GAP_PERIPHERAL_ROLE,$" "$GAP_C"; then
|
|
sed -i 's/^ GAP_PERIPHERAL_ROLE,$/ GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE | GAP_OBSERVER_ROLE,/' "$GAP_C"
|
|
echo ">> patched $GAP_C: GAP roles = peripheral|central|observer"
|
|
fi
|
|
|
|
# Export the ST BLE command API (aci_*/hci_*) to external apps.
|
|
WB_SCONS=lib/stm32wb.scons
|
|
python3 - "$WB_SCONS" <<'PY'
|
|
import re, sys
|
|
p = sys.argv[1]
|
|
s = open(p).read()
|
|
block = """# BT Inspector: expose the ST BLE command API to .fap apps
|
|
env.Append(
|
|
SDK_HEADERS=[
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_hci_le.h"),
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_gap_aci.h"),
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_gatt_aci.h"),
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_hal_aci.h"),
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_l2cap_aci.h"),
|
|
File("stm32wb_copro/wpan/ble/core/auto/ble_vs_codes.h"),
|
|
],
|
|
)
|
|
|
|
"""
|
|
s = re.sub(r"\n*# BT Inspector: expose.*?\n\)\n", "\n", s, flags=re.S) # drop any earlier copy
|
|
if block not in s:
|
|
s = s.replace('Return("lib")', block + 'Return("lib")', 1) # must run before Return
|
|
open(p, "w").write(s)
|
|
print(">> patched " + p + ": ST BLE headers added to SDK")
|
|
PY
|
|
|
|
# ST's generated headers have no extern "C" guards; the API table is C++.
|
|
python3 - <<'PY'
|
|
import re
|
|
for name in ["ble_hci_le", "ble_gap_aci", "ble_gatt_aci", "ble_hal_aci", "ble_l2cap_aci"]:
|
|
p = f"lib/stm32wb_copro/wpan/ble/core/auto/{name}.h"
|
|
s = open(p).read()
|
|
if "__cplusplus" in s:
|
|
continue
|
|
guard = re.search(r"#define \w+_H__\n", s).group(0)
|
|
s = s.replace(guard, guard + '#ifdef __cplusplus\nextern "C" {\n#endif\n', 1)
|
|
i = s.rstrip().rfind("#endif")
|
|
s = s[:i] + "#ifdef __cplusplus\n}\n#endif\n" + s[i:]
|
|
open(p, "w").write(s)
|
|
print(">> patched " + p + ': extern "C" guards')
|
|
PY
|
|
|
|
# Link (or refresh) the app into the user apps directory.
|
|
mkdir -p applications_user
|
|
rm -rf applications_user/bt_inspector
|
|
cp -r "$APP_SRC" applications_user/bt_inspector
|
|
|
|
FBT_ARGS=(
|
|
COMPACT=1 DEBUG=0 # release build, same as official images
|
|
COPRO_STACK_TYPE=ble_full
|
|
COPRO_STACK_BIN=stm32wb5x_BLE_Stack_full_fw.bin
|
|
)
|
|
|
|
API_CSV=targets/f7/api_symbols.csv
|
|
# BLE_API selects which ST command headers are exported to apps (each exported
|
|
# function is kept in the firmware image):
|
|
# hci,gatt scanning, connections, GATT client/server (~12 KB, default)
|
|
# all also gap, hal, l2cap (~20 KB)
|
|
# min only the functions BT Inspector imports (~1.5 KB)
|
|
BLE_API="${BLE_API:-hci,gatt}"
|
|
if grep -q "^Function,?," "$API_CSV" || ! grep -qE "^Header,\+,.*ble_hci_le\.h" "$API_CSV"; then
|
|
# fbt notices the new headers, rewrites the csv with '?' entries and stops.
|
|
echo ">> syncing API symbol table (expected to stop once)"
|
|
./fbt -j"${JOBS:-4}" "${FBT_ARGS[@]}" api_check || true
|
|
fi
|
|
python3 - "$API_CSV" "$BLE_API" "$APP_SRC" <<'PY'
|
|
import re, sys, glob
|
|
csv_path, mode, app_src = sys.argv[1:4]
|
|
hdrs = {"hci": "ble_hci_le", "gap": "ble_gap_aci", "gatt": "ble_gatt_aci", "hal": "ble_hal_aci", "l2cap": "ble_l2cap_aci"}
|
|
owner = {}
|
|
for key, name in hdrs.items():
|
|
src = open(f"lib/stm32wb_copro/wpan/ble/core/auto/{name}.h").read()
|
|
for fn in re.findall(r"^tBleStatus (\w+)\(", src, re.M):
|
|
owner[fn] = key
|
|
if mode == "all":
|
|
selected = set(owner)
|
|
elif mode == "min":
|
|
selected = set()
|
|
for f in glob.glob(app_src + "/*.c"):
|
|
selected |= set(re.findall(r"\b((?:aci_|hci_)\w+)\(", open(f).read()))
|
|
else:
|
|
want = set(mode.split(","))
|
|
selected = {fn for fn, key in owner.items() if key in want}
|
|
out, n_on = [], 0
|
|
for line in open(csv_path).read().splitlines():
|
|
parts = line.split(",")
|
|
if len(parts) >= 3 and parts[0] == "Function" and parts[2] in owner:
|
|
parts[1] = "+" if parts[2] in selected else "-"
|
|
n_on += parts[1] == "+"
|
|
elif len(parts) >= 2 and parts[1] == "?":
|
|
parts[1] = "-" if parts[0] == "Variable" else "+"
|
|
out.append(",".join(parts))
|
|
open(csv_path, "w").write("\n".join(out) + "\n")
|
|
print(f">> BLE API export ({mode}): {n_on} of {len(owner)} aci_*/hci_* functions enabled")
|
|
PY
|
|
|
|
if [ "${1:-}" = "fap" ]; then
|
|
./fbt -j"${JOBS:-4}" "${FBT_ARGS[@]}" fap_bt_inspector
|
|
echo ">> $(ls build/f7-firmware-C/.extapps/bt_inspector.fap)"
|
|
exit 0
|
|
fi
|
|
|
|
echo ">> building update package (full BLE stack + BT Inspector .fap)"
|
|
./fbt -j"${JOBS:-4}" "${FBT_ARGS[@]}" updater_package
|
|
|
|
PKG=$(ls -d dist/f7-C/f7-update-* 2>/dev/null | head -1)
|
|
echo
|
|
echo ">> update package: $PKG"
|
|
echo ">> radio stack in package:"
|
|
grep -E "^Radio" "$PKG/update.fuf" || true
|
|
echo ">> firmware size:"
|
|
ls -l "$PKG/firmware.dfu"
|
|
echo ">> app (also inside the package's resources, installed to apps/Bluetooth/):"
|
|
ls -l build/f7-firmware-C/.extapps/bt_inspector.fap
|
|
echo ">> SDK for building the app with ufbt: $(ls dist/f7-C/flipper-z-f7-sdk-*.zip)"
|
|
echo
|
|
echo "Install: copy '$PKG' to the SD card (e.g. /ext/update/) and run it from"
|
|
echo "the file browser, or use: ./build.sh flash"
|
|
|
|
if [ "${1:-}" = "flash" ]; then
|
|
echo ">> flashing over USB"
|
|
./fbt -j"${JOBS:-4}" "${FBT_ARGS[@]}" flash_usb_full
|
|
fi
|