* 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>
* 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>
Sub-GHz is the only main app still built into the firmware image, so everything
in it costs internal flash permanently. The Frequency Analyzer is its only
feature owning a view, a worker thread and a scene outright, so it moves to
applications/main/subghz_frequency_analyzer/ and builds into
subghz_frequency_analyzer.fal, mapped when its scene is entered and unmapped
when it is left. The app keeps a thunk scene, a loader, and a private API table
exporting the three app-internal functions the plugin calls.
Release build: 181,684 -> 185,004 bytes of free flash (+3,320). The .fal costs
5,103 bytes of heap while the screen is open and nothing when it is closed.
The sources had to leave applications/main/subghz/ entirely, because source
exclusions in a manifest are silently ignored for built-in apps. The plugin also
deploys to apps_data/subghz/plugins/features/ rather than the default
apps_data/subghz/plugins/, which the radio device registry scans on every
Sub-GHz start; a new optional fal_path in the app manifest allows that. The
underlying loader behaviour is filed as #1130.
Two ordering constraints are load-bearing and commented where they apply: the
OK-long scene transition stays in the app, since leaving the scene unmaps the
image the plugin would return into; and the plugin parks the dispatcher on an
app-owned view before removing its own, since removing the current view latches
an event loop stop and would otherwise quit Sub-GHz whenever the analyzer closed.
Also drops the analyzer model's write-only is_ext_radio, and stops the analyzer
view closing a notification record it never opened.
Hardware validated: entering the analyzer maps the plugin (free heap -8,984 B,
worker thread running at ~24% CPU), leaving it keeps Sub-GHz running and joins
the worker, and seven enter/leave cycles show no heap drift.
* Updater test: fail fast on hung copy + reboot-retry on bench
storage.py waits for timeout during a file copy
updater_test.yml splitted and added retry loop in a build-once / try -> hard-reset -> retry.
* Updater test: restore 5-minute step timeouts
* unit_tests: clear startup order
* fam: ensure unique STARTUP order
* fbt: warn on same .order values within a group leading to non-determinitic builds
* fbt: better formatting for app order warning
---------
Co-authored-by: hedger <hedger@nanode.su>
* libs: stricter constness for saving RAM with .rodata section; fbt: sdk: fixed signature generation for nested const params
* hal: additional fixes for constness in USB subsystem
* debug apps: additional usb-related fixes
* mjs: more consts for token parser
* fatfs: const driver struct
* hal: more consts for ble & nfc vars
* hal: made FuriHalSpiBusHandle static
* hal: made FuriHalI2cBusHandle static
* usb: restored previous api
* linter fixes
* API fixes
* Fix unaccessible flipper for binded access points
workaround to work with symlinked devices
* Fix return None if Flipper not started
* exception handling
* decreased timeouts
* Check environment variables for flipper path
* Move OTG controls to the power service
* Accessor: add missing power service import
* Power: add is_otg_enabled to info and properly handle OTG enable with VBUS voltage present
* Power: method naming
* Power: add backward compatibility with old-style use of furi_hal_power
* Scripts: lower MIN_GAP_PAGES to 1
* SubGhz: fix incorrect logging tag
* SubGhz: delegate OTG management to power service
* Power: fix condition race, various improvements
Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
* Furi, USB, BLE: extra stack space for some threads, small code cleanup.
* Furi: thread watermark check on exit, explicitly crash if built with LIB_DEBUG=1
* Debug: color logging in apps/furi gdb helper, check and show crash message in gdb console.
* Fix invalid path errors for non-Latin characters by enforcing UTF-8 (#4024)
Due to cryillic alphabet on `/openocd/scripts/target/1986ве1т.cfg`, If the system codepage is handling `WideChar` for cryillic properly, It would cause jumbled characters and fail to decompress via System.IO.Compression.ZipFile without Encoding enforcement. (See https://github.com/flipperdevices/flipperzero-firmware/issues/4024#issuecomment-2545385580)
* Scripts: fix line endings
Co-authored-by: あく <alleteam@gmail.com>
* extended unit_tests and changed to dockerized runner
* added branch to run units
* fixing unit-tests-output
* online output
* command not found fix
* added stm logging
* cleaned output
* Updated updater test to work on dockerized runner
* Test run for changed actions
* small refactor of run_unit_tests
* Final test of jobs
* Checked
* On-failure actions runs only on fail
* Set action trigger to pull request
* Bumped timeout a little
* Removed extra steps
* Removed stm monitor, as it's now part of docker-runner
* fix: testops without stm_monitoring
* fix: timeout extended
Co-authored-by: あく <alleteam@gmail.com>
* FuriHal, drivers: rework gauge initialization, ensure that we can recover from any kind of internal/external issue
* Make PVS happy
* Format sources
* bq27220: add gaps injection into write operations
* Drivers: bq27220 cleanup and various fixes
* Drivers: bq27220 verbose logging and full access routine fix
* Drivers: better cfg mode exit handling in bq27220 driver
* Drivers: rewrite bq27220 based on bqstudio+ev2400, experiments and guessing. Fixes all known issues.
* PVS: hello license check
* Drivers: minimize reset count in bq27220 init sequence
* Drivers: bq27220 hide debug logging, reorganize routine to ensure predictable result and minimum amount of interaction with gauge, add documentation and notes.
* Drivers: more reliable bq27220_full_access routine
* Drivers: replace some warning with error in bq27220
* Drivers: move static asserts to headers in bq27220
* Fix PVS warnings
* Drivers: simplify logic in bq27220
---------
Co-authored-by: hedger <hedger@users.noreply.github.com>