* fbt: make a built-in app's source exclusions actually exclude GatherSources() splits the list it is given into includes and "!exclusions" and applies the second to the first, so an exclusion only ever filters the patterns handed over in the same call. External apps pass their whole source list at once (fbt_extapps.py) and get what they asked for. The built-in path did not: get_builtin_app_folders() flattened each app to one (folder, pattern) pair per source entry, and firmware.scons called GatherSources() once per pair - so "*.c*" was gathered with no exclusions in sight and "!plugins" arrived alone with nothing to filter. Merging per app is not enough either. Sources are gathered per folder and a folder can hold more than one built-in app: applications/main/subghz has an APP beside a STARTUP hook, and the hook's default "*.c*" would re-glob whatever the app excluded. So collect one list per folder, merged across the built-in apps that live there, and pass it in a single call. That merge is also why GatherSources() now de-duplicates with dict.fromkeys() instead of a set(). Its result is the link order, and a folder that merges to more than one include pattern - applications/services/cli and applications/system/find_my_flipper both do - would otherwise have that order decided by PYTHONHASHSEED. Two builds of one tree could differ in ~10 KB of firmware.bin. External apps were already exposed to this; they no longer are. No built-in app in the tree declares an exclusion today, so this changes no output: with the version blob pinned (WORKFLOW_BRANCH_OR_TAG, DIST_SUFFIX, FORCE_NO_DIRTY, SOURCE_DATE_EPOCH) the firmware differs from dev only in the 8-byte embedded git hash and 2 bytes that track it, and two builds at different PYTHONHASHSEED values are byte-identical. What it unblocks is a built-in app keeping plugin sources in a subfolder the way NFC does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGHz: move the feature plugins into the app they belong to subghz_add_manually and subghz_frequency_analyzer sat in applications/main as siblings of nfc, lfrfid and the rest - directories that read as main apps, are not in the main_apps metapackage, and are not apps at all. They were there because a built-in app could not exclude sources: everything under applications/main/subghz is compiled into the firmware, and the six scene thunks the app keeps in scenes/ carry the same symbol names as the bodies that moved into the plugin, so the image would not have linked at all. The previous commit is a hard prerequisite, not an optimisation. They now live in applications/main/subghz/plugins/, declared in the app's own manifest with sources= pointing at their folders, which is how NFC keeps its plugins - 45 card parsers under plugins/supported_cards and 16 protocol-support plugins under helpers/protocol_support. The app excludes plugins/ from its own sources. Nothing about the output changes: same appids, so the same .fal files still deploy to apps_data/subghz/plugins, and with the version blob pinned the firmware differs from the previous commit only in the embedded git hash. The .fal files keep their size; their bytes differ only in the .gnu_debuglink CRC, which follows the debug ELF's DWARF now carrying the new source paths. The plugin sources keep including <subghz/...>, which still resolves - applications/main is on the global include path either way. One developer-facing wrinkle, shared with every other co-located plugin in the tree: fbt launch APPSRC=<path to the plugin folder> no longer resolves, because the path lookup matches the parent directory name and finds the built-in app. APPSRC=subghz_add_manually, by appid, still works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * Changelog Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
About
This folder contains supplementary scripts that automates routine actions. Flashing scripts are based on cli version of STM32CubeProgrammer. You will need to add STM32_Programmer_CLI to your path to use them.
Flashing empty MCU/Flipper
Always flash your device in the following sequence:
- OTP (Only on empty MCU)
- Core1 and Core2 firmware flashing
- Option Bytes
Otp flashing
!!! Flashing incorrect OTP may permanently brick your device !!!
Normally OTP data generated and flashed at the factory.
In case if MCU was replaced you'll need correct OTP data to be able to use companion applications.
Use otp.py to generate and flash OTP data.
You will need exact main board revision to generate OTP data. It can be found on main PCB.
Also display type, region and etc...
!!! Flashing incorrect OTP may permanently brick your device !!!
Core1 and Core2 firmware flashing
Core2 goes first, then Core1. Never flash FUS or you will lose your job, girlfriend and keys in secure enclave.
Option Bytes
!!! Setting incorrect Option Bytes may brick your MCU !!!
Defaults are mostly OK, but there are couple things that we'd like to tune. Also, OB may be damaged, so we've made couple scripts to check and set option bytes.
!!! Setting incorrect Option Bytes may brick your MCU !!!
Checking option bytes:
ob.py check
Setting option bytes:
ob.py set
Assets delivery
Build the firmware and run in the root folder of the repo:
python scripts/storage.py -p <flipper_cli_port> send build/latest/resources /ext
Slideshow creation
Put fullscreen slideshow frames in .png format into assets/slideshow/my_show folder, named frame_xx.png, where xx is zero-padded frame number, starting with #0.
Then run
python scripts/slideshow.py -i assets/slideshow/my_show/ -o assets/slideshow/my_show/.slideshow
Upload generated .slideshow file to Flipper's internal storage and restart it.