The RPC/loader commit added internal headers and relied on the "?" fallback that enabled every newly-discovered symbol. That exported internal loader helpers (loader_queue_*, loader_menu_alloc, ...) which are not in the firmware API-table link, breaking the full firmware build; the earlier per-app `fap` check did not link the table so it went unnoticed. Change the api_symbols.csv editor so newly-discovered Headers stay enabled (their declarations must reach the API table) but new Functions/Variables default to disabled unless explicitly listed in EXPORT_ENABLE. Drop the over-broad ^rpc_/^loader_ patterns (the public APIs are already exported by stock firmware). Result: internal loader types are available to apps, the internal helper functions stay unexported, and the full firmware links.
239 lines
10 KiB
Bash
Executable File
239 lines
10 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)"
|
|
APPS=(bt_inspector ble_hid_host) # app dirs under $HERE, each staged with libble/
|
|
APP_SRC="$HERE/bt_inspector" # used only for the BLE_API=min symbol scan
|
|
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
|
|
|
|
# Stage each app into applications_user, with the shared libble sources copied
|
|
# alongside so the fap glob (*.c) picks them up.
|
|
mkdir -p applications_user
|
|
for app in "${APPS[@]}"; do
|
|
rm -rf "applications_user/$app"
|
|
cp -r "$HERE/$app" "applications_user/$app"
|
|
cp "$HERE"/libble/*.c "$HERE"/libble/*.h "applications_user/$app/"
|
|
done
|
|
|
|
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}"
|
|
|
|
# --- Firmware API exported to .fap apps (in addition to the ST BLE commands) ---
|
|
# Each area is appended below; every enabled symbol stays in the firmware image.
|
|
# BLE peripheral internals: GAP control (advertised name, custom GATT server,
|
|
# extra-beacon config, connection state) and BLE-glue radio-stack status.
|
|
EXPORT_ENABLE="${EXPORT_ENABLE:-}"
|
|
EXPORT_ENABLE+='
|
|
^gap_
|
|
^ble_glue_(start|stop|get_c2_status|get_hardfault_info|force_c2_mode)$
|
|
^furi_hal_bt_init$
|
|
'
|
|
# Power / battery internals: the gas-gauge readings (voltage, current,
|
|
# temperature, capacity, health, charge state) are already exported by stock
|
|
# firmware; this adds the remaining safe reader. furi_hal_power_init stays
|
|
# disabled (re-initialising the power HAL from an app is unsafe).
|
|
EXPORT_ENABLE+='
|
|
^furi_hal_power_insomnia_level$
|
|
'
|
|
# Flash + option-byte internals: geometry, free-page info, and raw program /
|
|
# erase / write / option-byte access. These let an app read and modify internal
|
|
# flash directly. WARNING: misuse can brick the device or wipe storage; enabled
|
|
# here for research. Set EXPORT_FLASH=0 before building to leave them disabled.
|
|
if [ "${EXPORT_FLASH:-1}" = "1" ]; then
|
|
EXPORT_ENABLE+='
|
|
^furi_hal_flash_
|
|
'
|
|
fi
|
|
# RPC + loader internals: the public rpc_* / loader_* APIs (session open/feed/
|
|
# close, app data exchange, launch/enqueue/lock/menu) are already exported by
|
|
# stock firmware, so nothing is force-enabled here; this build only adds the
|
|
# internal loader header set so apps can use the internal Loader types. The
|
|
# internal helper functions stay unexported (see the "?" policy below): they are
|
|
# not linked into the API table, and exporting them breaks the firmware link.
|
|
export EXPORT_ENABLE
|
|
|
|
# Add the loader internal headers to the SDK (rpc_i.h is skipped: it pulls the
|
|
# protobuf headers, which are not part of the app SDK).
|
|
python3 - applications/services/loader/application.fam <<'PY'
|
|
import sys
|
|
p = sys.argv[1]
|
|
s = open(p).read()
|
|
want = ''' "loader.h",
|
|
"loader_i.h",
|
|
"loader_menu.h",
|
|
"loader_queue.h",
|
|
"loader_applications.h",
|
|
"firmware_api/firmware_api.h",'''
|
|
have = ''' "loader.h",
|
|
"firmware_api/firmware_api.h",'''
|
|
if "loader_i.h" not in s:
|
|
s = s.replace(have, want, 1)
|
|
open(p, "w").write(s)
|
|
print(">> patched " + p + ": loader internal headers added to SDK")
|
|
PY
|
|
if grep -q "^Function,?," "$API_CSV" || ! grep -qE "^Header,\+,.*ble_hci_le\.h" "$API_CSV" \
|
|
|| ! grep -qE "^Header,\+,.*loader_i\.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 os, 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}
|
|
# Firmware symbols to force-enable, one regex per line, from $EXPORT_ENABLE.
|
|
enable_res = [re.compile(p) for p in os.environ.get("EXPORT_ENABLE", "").split() if p]
|
|
out, n_on, n_fw = [], 0, 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) >= 3 and parts[0] in ("Function", "Variable") and any(r.search(parts[2]) for r in enable_res):
|
|
if parts[1] != "+":
|
|
n_fw += 1
|
|
parts[1] = "+"
|
|
elif len(parts) >= 2 and parts[1] == "?":
|
|
# Headers are always enabled (their declarations must be available to the
|
|
# API table). New functions/variables default to disabled: only the ones
|
|
# explicitly enabled (BLE commands, EXPORT_ENABLE) are exported, so
|
|
# internal helpers pulled in by added headers stay out of the table.
|
|
parts[1] = "+" if parts[0] == "Header" 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")
|
|
if enable_res:
|
|
print(f">> firmware API export: {n_fw} extra symbol(s) newly enabled")
|
|
PY
|
|
|
|
if [ "${1:-}" = "fap" ]; then
|
|
./fbt -j"${JOBS:-4}" "${FBT_ARGS[@]}" "${APPS[@]/#/fap_}"
|
|
ls build/f7-firmware-C/.extapps/*.fap
|
|
exit 0
|
|
fi
|
|
|
|
echo ">> building update package (full BLE stack + apps)"
|
|
./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 ">> apps (also inside the package's resources, installed to apps/Bluetooth/):"
|
|
ls -l build/f7-firmware-C/.extapps/*.fap
|
|
echo ">> SDK for building the apps 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
|