* FlipperApplication: skip a plugin that fails to load instead of ending the scan plugin_manager_load_all() broke out of the directory loop on the first file it could not load, so a single stray or stale .fal silently cost every plugin listed after it. It then returned PluginManagerErrorNone unconditionally, which made every caller's error check dead code - including the one in the Sub-GHz radio device registry, whose "Failed to load all libs" could never fire. Skip the file and carry on, and return the first error the scan hit, so a caller can tell a complete load from a partial one. An app id mismatch is deliberately not one of those errors. A scan selects, and a plugin belonging to another app is a normal thing to meet in a shared directory - reporting it would hand the caller an error for a folder that is working exactly as intended. That verdict belongs with the check itself rather than at the call site, so the body of plugin_manager_load_single() moves into a static helper that knows whether it is scanning: naming one file and getting a mismatch is still an error, meeting one mid-scan is logged at debug. Both examples checked the result and gave up, which was unreachable until now and would have thrown away the plugins that did load - the opposite of the point. They are the template a third-party plugin host gets copied from, so they log and carry on instead, and the do/while(0) whose only break this was goes with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * FlipperApplication: tell a failed directory read from the end of the directory storage_dir_read() returns false both when it has handed back the last entry and when the read itself failed - storage_ext_dir_read() sets FSE_NOT_EXIST for the former and leaves FSE_NOT_READY, FSE_INTERNAL and friends for the latter, so the only way to tell them apart is storage_file_get_error(). The scan loop treated the two the same, so a card pulled or a controller hiccup partway through left a half-populated manager reporting complete success. Check the error before the directory is closed, and report a read that ended the scan early. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * FlipperApplication: let a plugin directory scan filter by file name prefix A plugin's app id only becomes readable once its whole image has been mapped and relocated, its init arrays run and its entry point called - see flipper_application_plugin_get_descriptor(). So a scan of a directory shared with plugins of another kind pays for a foreign image in full, and runs its constructors against an API interface it was not built for, before it can tell the plugin is not the one it wanted. And apps_data/<parent appid>/plugins is fbt's default location for every plugin of an app, so sharing is the norm. Add plugin_manager_load_all_prefixed(), which takes an optional file name prefix and passes over anything that does not start with it. Same idea as CliCommandExternalConfig::fal_prefix in the CLI registry, which has its own loader for exactly this reason. plugin_manager_load_all() keeps its signature and becomes the NULL-prefix case, so nothing already built has to change. API 88.6 -> 88.7. Exporting the new entry point rather than keeping it firmware-internal costs 8 bytes of API table, and is what lets a plugin-hosting FAP fix the same hazard in its own directory. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGhz: pick the radio device plugins out of their folder by file name subghz_device_registry_init() scans apps_data/subghz/plugins on every subghz_devices_init(), which runs on every Sub-GHz app start and from the CLI and JS module. Anything else that ends up in that folder - the natural place for a Sub-GHz plugin, by fbt's own convention - was mapped, relocated and run in full before the app id said it was not a radio driver. For the Frequency Analyzer plugin that is 5,103 bytes of heap and ~7 KB of SD reads, every time. Radio device plugins are all named radio_device_*.fal already, so require that prefix and let the registry pass over the rest for the cost of a string compare. Its "Failed to load all libs" check now means something, and says which error it means. The cost of keying on the file name is that a driver named anything else stops being picked up, and a skip is only logged at debug level, which release builds compile out. Since a missing driver otherwise shows up as nothing more than the external module no longer being offered, warn when the scan ends with no driver at all, and add the assertion to the Sub-GHz unit test suite - which already inits the registry - that the one driver we ship is still found. The warning cannot see one of several drivers going missing; the test is what covers that if a second one is ever added. A third-party radio driver under some other name is the one compatibility cost here; radio_device_cc1101_ext.fal is the only driver in the tree and the only naming an out-of-tree one could have copied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * SubGhz: put the Frequency Analyzer plugin back in the default plugin directory PR #1131 deployed it to apps_data/subghz/plugins/features/ so the radio device registry's non-recursive scan would never see it, and added an fal_path field to the app manifest to allow that. With the registry filtering by file name and the loader skipping what it cannot load, the folder is safe to share, so the plugin goes back to apps_data/subghz/plugins/ and fal_path goes away with it. Nothing else used fal_path, and it has not shipped in a release, so no SDK consumer can be relying on it; every other plugin already deploys to the default apps_data/<parent appid>/plugins. It was also a divergence in scripts/fbt from OFW, which is rebase surface we do not need to carry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * CHANGELOG: plugin loader scan fix, API 88.7 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.