Files
22ce1d9ae5 RFID: choose which chip types a write may try (#1146)
* LFRFID: let a caller restrict which chips a write may target

The write loop tried every writable chip in turn - T5577, EM4305, then each
Hitag micro password variant - and verified after each one. A verify read
costs up to 2 s, so a pass that does not find the chip in front of you can
take 10 s before it starts over, most of it spent on chips the user knows are
not there.

Adds LFRFIDWriteTarget, which names a chip rather than an encoding: the Hitag
micro variants share one write type but differ by password, so each is its own
target. lfrfid_worker_set_write_targets() takes a mask of them, defaulting to
all, so a caller that never sets one behaves exactly as before.

lfrfid_settings holds that mask on the SD card next to the saved keys, so the
app, the CLI and any app writing through LFRFIDWorker agree on it without one
having to hand it to another.

The loop now intersects the mask with what the protocol can actually encode
for, once, before writing anything. That replaces the per-pass skip counter
(which re-fired "cannot be written" on every pass) and flattens the nested
variant loop into the single pass over targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: add a Settings menu with a Write Chips page

Settings sits at the bottom of the app menu and holds one page for now, where
each writable chip - T5577, EM4305 and the three Hitag micro / ID82xx variants
- can be switched on or off. Everything starts enabled, so a user who never
opens it sees no change.

The page itself lives in a plugin, loaded when the scene opens and unloaded
when it closes, since it is only reachable from one menu entry. The plugin
resolves against the firmware API alone - it needs no app-private symbols, so
there is no composite resolver or app symbol table here - and it is handed a
three-field context rather than LfRfid, which keeps the app's own layout out
of the plugin ABI.

The app keeps the VariableItemList: removing a view while it is the one on
screen latches the event loop stop, and the scene manager runs on_exit before
the previous scene's on_enter, so a plugin-owned view would be removed at
exactly the wrong moment. The list is reset before the plugin is freed, since
its change callbacks point into plugin code.

A missing or stale .fal costs only the ability to edit the setting: writes
read the mask from the settings file, not from the plugin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: say why a write cannot start instead of spinning

With every chip enabled the write screen always had something to try, so the
only failure it reported was the protocol being unwritable, and only after a
full pass. Now that chips can be switched off, two more ways to have nothing
to do exist, and they are worth telling apart: nothing enabled at all, and
nothing enabled that this protocol can be written to (HID, for instance, only
encodes for a T5577).

The check runs before the worker starts, so the error is immediate rather than
10 s of "Writing" first. Probing re-encodes the key, hence the snapshot before
it and the restore after.

The worker is only stopped on exit if it was actually started - stopping a
thread that never ran is not allowed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: honour the write chip setting in the CLI

rfid write drives the same worker as the app, so leaving it on the default
would have it quietly write to a chip the user switched off on the device.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: RFID write chip settings

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: rework the write chip settings after review

A 5-agent review and a 4-agent cleanup pass over the previous commits. The
feature is unchanged; what it costs and how it fails are not.

Settings file layout is no longer SDK ABI. saved_struct_load() copies as many
bytes as the *firmware* thinks the struct is, so exporting LFRFIDSettings meant
adding a field later would overflow the stack object of any .fap built against
the older header - and only the major API version gates loading, which this
would not have bumped. The struct, path, magic and version are now private to
lfrfid_settings.c and the SDK gets two scalar accessors.

A chip the user switched off is now its own outcome. The worker reported
LFRFIDWorkerWriteProtocolCannotBeWritten for both "this protocol has no
writable chip" and "every chip that could write it is disabled", so the CLI
told users their protocol was unwritable when the real fix was a setting. It
now reports LFRFIDWorkerWriteNoEnabledTarget for the second, which also lets
the write screen drop its own pre-flight probe: the scene ran the same
lfrfid_write_targets_supported() sweep the worker was about to run, purely to
pick the message, and paid a data snapshot/restore and a started-the-worker
flag for the privilege.

The settings page no longer leaves anything behind. variable_item_list_reset()
does not clear the enter callback - it lives outside the model and
set_enter_callback() rejects NULL - so a pointer into the unloaded plugin stayed
parked in app-owned state; the app now installs its own. The cached VariableItem
pointers are gone: they were justified by a deadlock that cannot happen (the
view model mutex is recursive, and subghz already calls
variable_item_list_get() from a change callback) and would have dangled once
the list outgrew M*LIB's initial capacity of 16.

A failed save is no longer silent. It reported nothing, and re-entering the page
showed the old values as though the user had chosen them. Saving moved to the
Back event, where the screen still belongs to this scene, and a failure goes
through dialog_message_show_storage_error() like the app's other storage errors.

The VariableItemList is allocated on first use. variable_item_list_alloc()
starts a periodic 333 ms timer it never stops, so allocating it at startup
capped tickless idle for the whole RFID session over a screen most users never
open.

Also: the static assert forbade the append the header told you to make, so
appending a target broke the build with a message about Hitag; the two ordering
asserts and the variant subtraction are replaced by a table whose rows state
their own variant. lfrfid_write_target_type() lost its default label, so
-Wswitch now forces a new target to be classified at compile time. The plugin
ABI lost its context struct - a page gets the list and the scene owns the view
switch, matching subghz. Four helpers with no caller outside the firmware are
marked "-" rather than exported for good. The plugin load shows the loading
animation and names the loader error, which separates a missing .fal from a
stale one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* LFRFID: second review pass over the write chip settings

Nine more agents over the reworked code. Four real defects, the rest comment
and shape corrections.

The lazily allocated VariableItemList was allocated before the plugin load, so
a failed load still started the periodic 333 ms timer the laziness exists to
avoid - for the rest of the session, on a screen the user could not use. It now
happens only once the page is known to be there.

lfrfid_settings_get_write_targets() stat'ed the file after trying to load it,
purely to pick a log level, so the missing-file case still went through
saved_struct_load()'s E log. It checks first and skips the load, which makes
the default path - every device whose owner never opened the screen - silent
again on every write.

The plugin-error popup was one character too long for the buffer and rendered
as "(error 1", because lfrfid_text_store_set() passed LFRFID_TEXT_STORE_SIZE
against a buffer that carries the extra byte for the terminator. Fixed at the
helper, so every caller gets its last character back.

The CLI never handled LFRFIDWorkerWriteTooLongToWrite, so a non-writable card
left it printing nothing while the app said "Still Trying to Write" on screen.
Both write callbacks now log an unhandled result instead of sending custom
event 0, which is not a LfRfidCustomEvent and would be dropped in silence.

Shape: the write scene's file static moved into the scene manager's own
per-scene state, lfrfid_settings_load() folded into its only caller, the write
loop's "done" flag stopped standing in for "never started", and the write type
switch lost its default so -Wswitch covers it like the target switch (a probe
build confirms an unclassified enumerator is now a build error). The <= 32
assert became < 32: at exactly 32 targets LFRFID_WRITE_TARGET_MASK_ALL shifts
by the full width of its type.

Comments: five said things that were not true - that the error code tells a
missing .fal from a stale one (plugin_manager collapses both into one value),
that a failed settings read had no other trace (saved_struct logs all four
causes), that the loading view swallows input (leaving it resets the input
queue), that opening the settings page writes a file, and that a scene's screen
belongs to the next scene by the time on_exit runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* CHANGELOG: annotate the RFID write chip entry with its issue and PR

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: MX <10697207+xMasterX@users.noreply.github.com>
2026-09-14 08:40:22 +03:00

29 lines
618 B
Python

Import("env")
env.Append(
LINT_SOURCES=[
Dir("."),
],
CPPPATH=[
"#/lib/lfrfid",
],
SDK_HEADERS=[
File("lfrfid_worker.h"),
File("lfrfid_raw_worker.h"),
File("lfrfid_raw_file.h"),
File("lfrfid_dict_file.h"),
File("lfrfid_settings.h"),
File("lfrfid_write_targets.h"),
File("protocols/lfrfid_protocols.h"),
],
)
libenv = env.Clone(FW_LIB_NAME="lfrfid")
libenv.ApplyLibFlags()
sources = libenv.GlobRecursive("*.c*")
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
libenv.Install("${LIB_DIST_DIR}", lib)
Return("lib")