From 724abde8ecbdaa4884465f3729cc98ca0ce77e91 Mon Sep 17 00:00:00 2001 From: d4rks1d33 Date: Sat, 19 Sep 2026 17:16:58 +0000 Subject: [PATCH] This should work... --- EXTERNAL_LIBS.md | 218 ++++++ .../main/nfc/helpers/nfc_emv_parser.h | 8 + .../helpers/protocol_support/emv/emv_render.c | 6 + .../helpers/protocol_support/emv/emv_render.h | 8 + applications/system/ProtoPirate/README.md | 2 +- .../ProtoPirate/api/protopirate_api_table_i.h | 130 ++++ .../system/ProtoPirate/application.fam | 38 +- .../ProtoPirate/helpers/protopirate_models.c | 285 ++++++++ .../ProtoPirate/helpers/protopirate_models.h | 20 + .../protopirate_protocol_plugin_host.c | 4 +- .../helpers/protopirate_psa_bf_host.c | 6 +- .../helpers/protopirate_settings.c | 49 +- .../helpers/protopirate_settings.h | 3 + .../ProtoPirate/helpers/protopirate_storage.c | 30 +- .../ProtoPirate/helpers/protopirate_storage.h | 8 +- .../helpers/protopirate_tool_scene_host.c | 2 +- .../ProtoPirate/helpers/protopirate_txrx.c | 14 + .../ProtoPirate/helpers/protopirate_types.h | 1 + .../ProtoPirate/helpers/protopirate_views.c | 27 +- .../ProtoPirate/helpers/protopirate_views.h | 1 + .../ProtoPirate/helpers/variable_item_list.c | 688 ++++++++++++++++++ .../ProtoPirate/helpers/variable_item_list.h | 159 ++++ .../system/ProtoPirate/keystore/models.txt | 33 + .../system/ProtoPirate/protocols/fiat_v1.c | 9 +- .../system/ProtoPirate/protocols/ford_v0.c | 10 +- .../system/ProtoPirate/protocols/kia_v0.c | 4 +- .../system/ProtoPirate/protocols/renault_v0.c | 24 +- .../system/ProtoPirate/protocols/renault_v1.c | 137 ++-- .../system/ProtoPirate/protocols/renault_v1.h | 2 +- .../system/ProtoPirate/protopirate_app.c | 145 +++- .../system/ProtoPirate/protopirate_app_i.h | 25 +- .../plugins/protopirate_config_plugin.c | 494 +++++++++++++ .../plugins/protopirate_config_plugin.h | 34 + .../plugins/protopirate_emulate_plugin.c | 43 +- .../plugins/protopirate_psa_bf_plugin.c | 29 +- .../scenes/protopirate_scene_emulate.c | 18 +- .../scenes/protopirate_scene_receiver.c | 56 +- .../protopirate_scene_receiver_config.c | 294 +------- .../scenes/protopirate_scene_receiver_info.c | 51 +- .../scenes/protopirate_scene_start.c | 6 +- .../scenes/protopirate_scene_sub_decode.c | 123 +++- .../scenes/protopirate_scene_timing_tuner.c | 7 + .../ProtoPirate/views/protopirate_receiver.c | 68 +- lib/subghz/protocols/psa.c | 174 ++--- lib/subghz/protocols/toyota.c | 252 ++++++- lib/toolbox/SConscript | 1 + targets/f7/api_symbols.csv | 12 +- 47 files changed, 3105 insertions(+), 653 deletions(-) create mode 100644 applications/system/ProtoPirate/helpers/protopirate_models.c create mode 100644 applications/system/ProtoPirate/helpers/protopirate_models.h create mode 100644 applications/system/ProtoPirate/helpers/variable_item_list.c create mode 100644 applications/system/ProtoPirate/helpers/variable_item_list.h create mode 100644 applications/system/ProtoPirate/keystore/models.txt create mode 100644 applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.c create mode 100644 applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.h diff --git a/EXTERNAL_LIBS.md b/EXTERNAL_LIBS.md index 7e37f3ea..af7b2380 100644 --- a/EXTERNAL_LIBS.md +++ b/EXTERNAL_LIBS.md @@ -84,3 +84,221 @@ the app keep its vendored protocols: runtime, so it cannot be linked against the current private lib safely. The app must be updated to the current SubGhz block API before re-enabling. Its `application.fam` is renamed to `application.fam.disabled`. + +## toolbox symbols exported for the private libs + +Because `lib/subghz`, `lib/nfc`, `lib/lfrfid` are no longer linked into the +firmware, any *firmware* symbol they call must be part of the public SDK API +(`targets/f7/api_symbols.csv`) so the app loader can resolve it at runtime. +Most already were, but some `lib/toolbox` helpers used only internally by these +libs were not exported. If you extract another lib or bump these libs and hit +`MissingImports`/`unresolved` for a `lib/toolbox` (or other firmware) function, +export it the SDK way — **do not hand-edit the CSV blindly**: + +1. Add the header to `SDK_HEADERS` in the owning lib's SConscript + (e.g. `File("buffer_stream.h")` in `lib/toolbox/SConscript`). +2. Build once — the SDK checker stops with "API version is still WIP" and writes + the new entries into `targets/f7/api_symbols.csv` with status `?` + (and bumps `Version` to `v`). +3. Flip those `?` to `+` and the `Version,v,X.Y` to `Version,+,X.Y`, then rebuild. + +Already exported this way: the 9 `buffer_stream.h` symbols (`buffer_get_data`, +`buffer_get_size`, `buffer_reset`, `buffer_stream_alloc`, `buffer_stream_free`, +`buffer_stream_get_overrun_count`, `buffer_stream_receive`, `buffer_stream_reset`, +`buffer_stream_send_from_isr`) — needed by `lib/nfc` (nfc.c, mf_classic, felica…) +and `lib/lfrfid` (raw worker). `mbedtls_des3_*` stay `-` (NOT exported): apps +that need them link `mbedtls` statically via `fap_libs=["mbedtls"]`. + +## Where the private libs come from + +The private lib each app carries is **the same firmware library**, reached +through a relative symlink — there is no separate copy on disk: + +- `applications/*//lib/subghz -> ../../../../lib/subghz` +- `applications/*//lib/nfc -> ../../../../lib/nfc` +- `applications/*//lib/lfrfid -> ../../../../lib/lfrfid` + +(4 `../` for apps at `applications///`.) The `fap_private_libs` +`Lib(name=...)` then compiles that symlinked tree into a per-app static lib. +So edits to `lib/subghz` etc. affect both the firmware headers and every app's +private lib automatically. + +--- + +# Updating a vendored SubGhz app (ProtoPirate case study) + +ProtoPirate (`applications/system/ProtoPirate`) is a vendored-protocol SubGhz app +tracked from upstream `https://protopirate.net/ProtoPirate/ProtoPirate`. It is +NOT a git submodule/subtree — it lives inside this repo, so updates are a manual +"copy upstream + re-apply our XIP adaptations". Everything we change lives in +**files under our control** (`application.fam` + `api/`); upstream `.c`/`.h` +files are kept byte-identical so future merges stay easy. + +## The 4 XIP adaptations that MUST survive an update + +1. **`lib/subghz` symlink** — `applications/system/ProtoPirate/lib/subghz -> + ../../../../lib/subghz`. Preserve it (don't delete `lib/`). +2. **`api/` table** — `api/protopirate_api_table.cpp` (defines + `subghz_application_api_interface`) + `api/protopirate_api_table_i.h` (the + symbol list). These expose the SubGhz lib symbols the runtime `.fal` plugins + import. Preserve both. +3. **Resolver wiring in the 3 host files** — each of + `helpers/protopirate_protocol_plugin_host.c`, + `helpers/protopirate_psa_bf_host.c`, + `helpers/protopirate_tool_scene_host.c` needs, after its + `#include `: + ```c + extern const ElfApiInterface* const subghz_application_api_interface; + ``` + and, right after every `composite_api_resolver_add(resolver, firmware_api_interface);`: + ```c + composite_api_resolver_add(resolver, subghz_application_api_interface); + ``` + (2 sites in protocol_plugin_host.c, 1 each in the other two.) +4. **`application.fam`** — the `_PROTOPIRATE_SUBGHZ_LIB = Lib(name="subghz", … + sources=["*.c*","blocks/*.c*","devices/*.c*","devices/cc1101_int/*.c*", + "protocols/base.c"])` block (curated: infra only, NOT the vendored concrete + protocols), plus on the main `proto_pirate` App: `sources=_MAIN_APP_SOURCES + + ["!lib"]`, `fap_libs=["hwdrivers"]`, `fap_private_libs=[_PROTOPIRATE_SUBGHZ_LIB]`, + `stack_size=8*1024`. + +## Step-by-step update procedure + +```bash +# 1. Clone upstream to a temp dir +git clone --depth 1 https://protopirate.net/ProtoPirate/ProtoPirate.git /tmp/pp +DEST=applications/system/ProtoPirate + +# 2. See what changed / is new (excluding our api/ and lib/) +for f in $(cd /tmp/pp && find . -type f -not -path './.git*'); do + if [ -f "$DEST/$f" ]; then diff -q "$DEST/$f" "/tmp/pp/$f" >/dev/null || echo "CHANGED: $f"; + else echo "NEW: $f"; fi; done + +# 3. Copy everything EXCEPT application.fam (merge that by hand) and never touch api/ or lib/ +for f in $(cd /tmp/pp && find . -type f -not -path './.git*' -not -name application.fam); do + mkdir -p "$DEST/$(dirname "$f")"; cp "/tmp/pp/$f" "$DEST/$f"; done +``` + +4. Re-apply adaptation **#3** to the 3 host files (they got overwritten). +5. Merge **#4**: `cp /tmp/pp/application.fam $DEST/application.fam`, then re-inject + the private-lib block + the main-app XIP fields (see above). Keep any NEW + upstream apps/plugins/sources from the merge. +6. Build the app only, then verify plugins resolve (see next section). Any newly + imported SubGhz symbol goes into `api/protopirate_api_table_i.h`; any newly + imported app-level (`protopirate_*`) symbol means a plugin needs an extra `.c` + added to its `sources` in the fam. + +## Gotchas learned from the v3.4 update + +- **New plugins that drag in the host stack.** v3.4 added + `protopirate_config_plugin`, which compiles `protopirate_txrx.c` + + `protopirate_protocol_plugin_host.c` and therefore pulls the whole RX radio + + plugin-loader stack. To make that `.fal` self-contained we added to its + `sources`: `helpers/protopirate_radio.c`, `helpers/radio_device_loader.c`, + `views/protopirate_receiver.c`, `protopirate_history.c`, + `helpers/protopirate_storage.c`, `protocols/protocols_common.c` (defines the + `FF_*` flipper-format key strings), and `api/protopirate_api_table.cpp` + (defines `subghz_application_api_interface`). +- **Generated icons header name.** `views/protopirate_receiver.c` does + `#include "proto_pirate_icons.h"`. That header is generated from the app's + `fap_icon_assets` and named `_icons.h`. For a plugin whose appid differs, + set `fap_icon_assets_symbol="proto_pirate"` in its App() so the generated header + matches the upstream include — this avoids editing upstream sources. +- **Resolving is iterative.** Adding one `.c` can pull in a new family of + undefined symbols (e.g. `radio_device_loader.c` pulled `subghz_devices_begin/ + end/get_by_name/is_connect`). Re-run the verify command after each change until + it prints nothing. + +--- + +# Troubleshooting a broken external SubGhz / NFC / RFID app + +Symptoms and how to chase them down. The root cause is almost always a symbol the +private lib (or a plugin) needs that is neither in the firmware SDK +(`api_symbols.csv`) nor in the app's own API table. + +## Symptom → meaning + +- On device, opening the app **reboots with an out-of-memory crash**, or the log + shows `XIP: unresolved fast rel record N` and then cuts off → a symbol used by + the XIP'd `.fap` is unresolved; the XIP relocation aborts mid-stream. +- `Status [3]: … MissingImports` in the loader log → same root cause, but the app + fit in RAM (no XIP) so it failed cleanly instead of crashing. +- `[E][Fap] Failed to preload …` while browsing → the `.fap` header/manifest + couldn't be read (usually a stale/othertarget build), not a symbol issue. + +## Fast offline check (do this before flashing) + +Compare the app's *undefined* symbols against everything that can resolve them +(firmware SDK + the app's own API table). Anything left over (minus known +libgcc/linker builtins) is a real problem. + +```bash +NM=/opt/postmarket/test/Flipper-ARF/toolchain/current/bin/arm-none-eabi-nm +ELF=build/f7-firmware-C/.extapps/_d.elf # or a *_plugin_d.elf + +# symbols exported by the firmware SDK +grep -E "^(Function|Variable),\+," targets/f7/api_symbols.csv | awk -F, '{print $3}' | sort -u > /tmp/resolvable.txt +# (SubGhz vendored apps also add their own table; append it:) +grep -A1 "API_METHOD(\|API_VARIABLE(" applications/system//api/*_api_table_i.h \ + | grep -vE "API_METHOD|API_VARIABLE|^--" | tr -d ' ,' >> /tmp/resolvable.txt +sort -u -o /tmp/resolvable.txt /tmp/resolvable.txt + +# undefined in the app that nothing can resolve (ignore builtins) +"$NM" -u "$ELF" 2>/dev/null | awk '{print $2}' | sort -u \ + | comm -23 - /tmp/resolvable.txt \ + | grep -vE "^__|uxTopUsedPriority" +``` + +Known-harmless leftovers (resolved at final `.fal`/loader link): `__aeabi_*`, +`__paritysi2`, `__popcountsi2`, `uxTopUsedPriority`. Also ignore `FF_*` style +`extern const char[]` if the app defines them in one of its own `.c` (e.g. +`protocols_common.c`) — check with `grep`. + +## Fix depending on where the symbol lives + +- **Symbol is a firmware/`lib/toolbox` function not in the SDK** → export it via + `SDK_HEADERS` (see "toolbox symbols exported" above). Fixes the app AND every + other external app. +- **Symbol is a SubGhz lib function** and the app is a vendored SubGhz app with + `.fal` plugins → add it to that app's `api/*_api_table_i.h` (with the exact + signature from the lib header) so the plugin resolver can satisfy it. Include + the relevant `subghz/*.h` at the top of the table if needed. +- **Symbol is an app-level function** (`_*`) undefined in a `.fal` plugin → + the plugin compiles a `.c` that *calls* it but not the `.c` that *defines* it. + Add the defining `.c` to that plugin's `sources` in `application.fam`. Watch for + cascades (adding a `.c` can introduce new undefined symbols) and for duplicate + definitions (don't add a `.c` whose symbols another listed source already + defines). +- **Symbol is a `mbedtls_*`/`bit_lib`/`assets` function** → the app must link that + static lib: `fap_libs=["mbedtls", "bit_lib", "assets"]` (nfc needs all three; + subghz needs none). + +## NFC-specific notes + +- Every external NFC app needs `fap_libs=["mbedtls","bit_lib"]` (the nfc lib uses + both) plus the `nfc` private lib. `furi_hal_nfc` stays in the firmware SDK. +- App API table for the bundled NFC app is + `applications/main/nfc/api/nfc_app_api_table_i.h` (exposes the lib symbols the + card parsers / protocol `.fal` plugins import). If a parser plugin fails to + resolve, add the missing lib symbol there. +- **C vs C++ linkage trap:** the api table is a `.cpp`. Any app-layer header it + includes to reference a symbol MUST have `extern "C"` guards, or C++ name + mangling makes the symbol undefined. This bit us with + `helpers/protocol_support/emv/emv_render.h` and `helpers/nfc_emv_parser.h` + (they lacked the guards; every other table header had them). Symptom: nm shows + both a mangled `_Z…name` and a plain `name` undefined. +- A symbol that is *declared + called + listed in the table but never defined* + (dead code upstream tolerates via `--gc-sections`) becomes undefined once the + table takes its address. We hit `nfc_render_emv_name` (had no definition in + Moon/Momentum either) and had to define it in `emv_render.c`. + +## RFID-specific notes + +- External RFID apps carry the `lfrfid` private lib (symlink + + `fap_private_libs=[Lib(name="lfrfid", …)]`). No `mbedtls` needed. +- RFID apps are usually small enough to load into RAM (log: + `App fits in RAM … skipping XIP`), so unresolved symbols show as a clean + `MissingImports` rather than a reboot. Same fix paths as above — most commonly + a missing `lib/toolbox` export (this is exactly how the `buffer_stream_*` gap + surfaced first on `lfrfid.fap`). diff --git a/applications/main/nfc/helpers/nfc_emv_parser.h b/applications/main/nfc/helpers/nfc_emv_parser.h index 03edab0a..cc760b89 100644 --- a/applications/main/nfc/helpers/nfc_emv_parser.h +++ b/applications/main/nfc/helpers/nfc_emv_parser.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + /** Get EMV application name by number * @param storage Storage instance * @param aid - AID number array @@ -38,3 +42,7 @@ bool nfc_emv_parser_get_currency_name( Storage* storage, uint16_t currency_code, FuriString* currency_name); + +#ifdef __cplusplus +} +#endif diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c index 9a586fe4..3f105c5a 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.c +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.c @@ -30,6 +30,12 @@ void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* s furi_string_cat_printf(str, "\n"); } +void nfc_render_emv_name(const char* data, FuriString* str) { + if(data == NULL || data[0] == '\0') return; + + furi_string_cat_printf(str, "Name: %s\n", data); +} + void nfc_render_emv_data(const EmvData* data, FuriString* str) { nfc_render_emv_pan(data->emv_application.pan, data->emv_application.pan_len, str); nfc_render_emv_name(data->emv_application.application_name, str); diff --git a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h index de2e20eb..80b2a8f9 100644 --- a/applications/main/nfc/helpers/protocol_support/emv/emv_render.h +++ b/applications/main/nfc/helpers/protocol_support/emv/emv_render.h @@ -5,6 +5,10 @@ #include "../nfc_protocol_support_render_common.h" #include +#ifdef __cplusplus +extern "C" { +#endif + void nfc_render_emv_info(const EmvData* data, NfcProtocolFormatType format_type, FuriString* str); void nfc_render_emv_data(const EmvData* data, FuriString* str); @@ -28,3 +32,7 @@ void nfc_render_emv_transactions(const EmvApplication* data, FuriString* str); void nfc_render_emv_uid(const uint8_t* uid, const uint8_t uid_len, FuriString* str); void nfc_render_emv_header(FuriString* str); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/ProtoPirate/README.md b/applications/system/ProtoPirate/README.md index ae939c83..f0100c56 100644 --- a/applications/system/ProtoPirate/README.md +++ b/applications/system/ProtoPirate/README.md @@ -52,7 +52,7 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch | Ford V1 | ✅ | ✅ | Manchester | F4 | Rolling Code | CRC16 | 315.00 / 433.92 | | Ford V2 | ✅ | ✅ | Manchester | F4 | Rolling Code (simple replay) | ❌ | 434.25 | | Ford V3 | ✅ | ❌ | Manchester | F4 | Rolling Code | ❌ | 434.25 | -| Honda Static | ✅ | ✅ | PWM | Honda1 | Static Code | Checksum | 315.00 / 433.92 | +| Honda Static | ✅ | ✅ | PWM | Honda1 | Static Code | Checksum | 433.65 | | Kia V0 / Suzuki V0 / Honda V0 / Mitsubishi V0 | ✅ | ✅ | PWM | FM476 | Rolling Code | CRC8 | 315.00 / 433.92 | | Kia V2 | ✅ | ✅ | Manchester | FM476 | Rolling Code | CRC4 | 315.00 / 433.92 | | Kia V3 / V4 | ✅ | ✅ | PWM | FM476 | KeeLoq | CRC4 (BF) | 315.00 / 433.92 | diff --git a/applications/system/ProtoPirate/api/protopirate_api_table_i.h b/applications/system/ProtoPirate/api/protopirate_api_table_i.h index ca8e0f64..fa05dcd5 100644 --- a/applications/system/ProtoPirate/api/protopirate_api_table_i.h +++ b/applications/system/ProtoPirate/api/protopirate_api_table_i.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +159,135 @@ static constexpr auto subghz_app_api_table = sort(create_array_t( subghz_transmitter_yield, LevelDuration, (void* context)), + /* RX-path symbols imported by protopirate_config_plugin (v3.4) */ + API_METHOD( + subghz_devices_init, + void, + (void)), + API_METHOD( + subghz_devices_deinit, + void, + (void)), + API_METHOD( + subghz_devices_begin, + bool, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_end, + void, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_get_by_name, + const SubGhzDevice*, + (const char* device_name)), + API_METHOD( + subghz_devices_is_connect, + bool, + (const SubGhzDevice* device)), + API_METHOD( + subghz_environment_alloc, + SubGhzEnvironment*, + (void)), + API_METHOD( + subghz_environment_free, + void, + (SubGhzEnvironment* instance)), + API_METHOD( + subghz_environment_load_keystore, + bool, + (SubGhzEnvironment* instance, const char* filename)), + API_METHOD( + subghz_devices_flush_rx, + void, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_get_rssi, + float, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_is_frequency_valid, + bool, + (const SubGhzDevice* device, uint32_t frequency)), + API_METHOD( + subghz_devices_set_async_mirror_pin, + void, + (const SubGhzDevice* device, const GpioPin* gpio)), + API_METHOD( + subghz_devices_set_rx, + void, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_sleep, + void, + (const SubGhzDevice* device)), + API_METHOD( + subghz_devices_start_async_rx, + void, + (const SubGhzDevice* device, void* callback, void* context)), + API_METHOD( + subghz_devices_stop_async_rx, + void, + (const SubGhzDevice* device)), + API_METHOD( + subghz_environment_set_protocol_registry, + void, + (SubGhzEnvironment* instance, const SubGhzProtocolRegistry* protocol_registry_items)), + API_METHOD( + subghz_receiver_alloc_init, + SubGhzReceiver*, + (SubGhzEnvironment* environment)), + API_METHOD( + subghz_receiver_free, + void, + (SubGhzReceiver* instance)), + API_METHOD( + subghz_receiver_set_filter, + void, + (SubGhzReceiver* instance, SubGhzProtocolFlag filter)), + API_METHOD( + subghz_setting_get_default_frequency, + uint32_t, + (SubGhzSetting* instance)), + API_METHOD( + subghz_setting_get_frequency, + uint32_t, + (SubGhzSetting* instance, size_t idx)), + API_METHOD( + subghz_setting_get_frequency_count, + size_t, + (SubGhzSetting* instance)), + API_METHOD( + subghz_setting_get_frequency_default_index, + uint32_t, + (SubGhzSetting* instance)), + API_METHOD( + subghz_setting_get_hopper_frequency, + uint32_t, + (SubGhzSetting* instance, size_t idx)), + API_METHOD( + subghz_setting_get_hopper_frequency_count, + size_t, + (SubGhzSetting* instance)), + API_METHOD( + subghz_worker_free, + void, + (SubGhzWorker* instance)), + API_METHOD( + subghz_worker_is_running, + bool, + (SubGhzWorker* instance)), + API_METHOD( + subghz_worker_rx_callback, + void, + (bool level, uint32_t duration, void* context)), + API_METHOD( + subghz_worker_start, + void, + (SubGhzWorker* instance)), + API_METHOD( + subghz_worker_stop, + void, + (SubGhzWorker* instance)), API_VARIABLE(subghz_device_cc1101_preset_ook_270khz_async_regs, const uint8_t[]), API_VARIABLE(subghz_device_cc1101_preset_ook_650khz_async_regs, const uint8_t[]), API_VARIABLE(subghz_device_cc1101_preset_2fsk_dev2_38khz_async_regs, const uint8_t[]), diff --git a/applications/system/ProtoPirate/application.fam b/applications/system/ProtoPirate/application.fam index 42b53888..cd948032 100644 --- a/applications/system/ProtoPirate/application.fam +++ b/applications/system/ProtoPirate/application.fam @@ -17,6 +17,8 @@ _ENABLE_EMULATE = ProtoPirateDefineEnabled("ENABLE_EMULATE_FEATURE") _MAIN_APP_SOURCES = [ "*.c*", + "!helpers/protopirate_models.c", + "!helpers/variable_item_list.c", "!protocols/plugins", "!scenes/plugins", "!protocols", @@ -69,7 +71,7 @@ App( requires=["gui"], stack_size=8 * 1024, fap_description="Decode car key fob signals from Sub-GHz", - fap_version="3.3", + fap_version="3.4", fap_icon="images/protopirate_10px.png", fap_category="Sub-GHz", fap_icon_assets="images", @@ -443,3 +445,37 @@ App( fap_icon_assets="images", fal_embedded=True, ) + +App( + appid="protopirate_config_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="protopirate_config_plugin_ep", + requires=["proto_pirate"], + sources=[ + "helpers/protopirate_protocol_plugin_host.c", + "helpers/protopirate_models.c", + "helpers/protopirate_txrx.c", + "helpers/variable_item_list.c", + "protocols/protocol_items.c", + "protocols/protocols_common.c", + "scenes/plugins/protopirate_config_plugin.c", + # XIP adaptation: SubGhz lib is private to this app, so the config + # plugin (which drags in the protocol-plugin host + RX radio stack via + # protopirate_txrx.c) must be self-contained. These app-level sources + # define the protopirate_* symbols the host stack references, and the + # api table provides subghz_application_api_interface for the nested + # protocol-registry plugin resolver. + "helpers/protopirate_radio.c", + "helpers/radio_device_loader.c", + "views/protopirate_receiver.c", + "protopirate_history.c", + "helpers/protopirate_storage.c", + "api/protopirate_api_table.cpp", + ], + fap_icon_assets="images", + # Generate the icon bundle header as "proto_pirate_icons.h" (matching the + # main app) so the upstream views/protopirate_receiver.c include resolves + # unchanged — keeps upstream sources untouched for easy future updates. + fap_icon_assets_symbol="proto_pirate", + fal_embedded=True, +) \ No newline at end of file diff --git a/applications/system/ProtoPirate/helpers/protopirate_models.c b/applications/system/ProtoPirate/helpers/protopirate_models.c new file mode 100644 index 00000000..650b26be --- /dev/null +++ b/applications/system/ProtoPirate/helpers/protopirate_models.c @@ -0,0 +1,285 @@ +#include "protopirate_models.h" + +#define TAG "ProtoPirateModels" + +#define PROTOPIRATE_MODELS_FILE APP_ASSETS_PATH("models.txt") + +//Models File Format. +#define MODELS_FILE_HEADER "ProtoPirate Car Models Database" +#define MODELS_FILE_VERSION 2 +#define MODELS_FIELD_MODEL_COUNT "ModelCount" +#define MODELS_FIELD_MODEL_NAME "ModelName" +#define MODELS_FIELD_FREQUENCY "Frequency" +#define MODELS_FIELD_PRESET_NAME "PresetName" +#define MODELS_FIELD_CUSTOM_PRESET_DATA "CustomPresetData" + +bool car_model_get_by_index( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t car_models_count, + SubGhzSetting* app_settings) { + //Temp variables + uint32_t version = 0; + uint32_t frequency; + uint8_t* preset_data = NULL; + uint32_t preset_data_size = 0; + bool error = true; + bool custom_preset = false; + + //Objects that need allocating. + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + FuriString* preset_name = furi_string_alloc(); + FuriString* model_name = furi_string_alloc(); + FuriString* header = furi_string_alloc(); + + //Index 0 is None. + switch(index) { + case 0: + //Leave as same as error (none) + break; + default: { + //Open Models File, check the Header, check Version + if(!flipper_format_file_open_existing(ff, PROTOPIRATE_MODELS_FILE)) { + FURI_LOG_I(TAG, "models.txt file not found"); + break; + } else if(!flipper_format_read_header(ff, header, &version)) { + FURI_LOG_W(TAG, "Failed to read models.txt header"); + break; + } else if(furi_string_cmp_str(header, MODELS_FILE_HEADER) != 0) { + FURI_LOG_W(TAG, "Invalid models.txt file header"); + break; + } else if(version != MODELS_FILE_VERSION) { + FURI_LOG_W(TAG, "Unsupported models.txt version %lu", (unsigned long)version); + break; + } + + //Fields we add the number to for searching the file. + char model_name_index[16]; //Frequency65535 + NULL + char frequency_index[16]; //Frequency65535 + NULL + char preset_name_index[18]; //PresetName65535 + NULL + char preset_data_index[23]; //CustomPresetData65535 + NULL + + //Build the values we need to grab. + snprintf(model_name_index, sizeof(model_name_index), MODELS_FIELD_MODEL_NAME "%u", index); + snprintf(frequency_index, sizeof(frequency_index), MODELS_FIELD_FREQUENCY "%u", index); + snprintf( + preset_name_index, sizeof(preset_name_index), MODELS_FIELD_PRESET_NAME "%u", index); + snprintf( + preset_data_index, + sizeof(preset_data_index), + MODELS_FIELD_CUSTOM_PRESET_DATA "%u", + index); + + //Read the Model Name. + if(!flipper_format_read_string(ff, model_name_index, model_name)) { + FURI_LOG_E("ProtoPirate", "Failed to read %s", model_name_index); + break; + } + + // Read frequency + if(!flipper_format_read_uint32(ff, frequency_index, &frequency, 1)) { + FURI_LOG_W(TAG, "Failed to read frequency."); + frequency = 0; + break; + } + + //Read the Modulation Preset. + if(!flipper_format_read_string(ff, preset_name_index, preset_name)) { + //Set the Custom preset name. + furi_string_set_str(preset_name, "Custom"); + custom_preset = true; + } else { + //Get the built in preset if it exists + const char* preset_name_long = furi_string_get_cstr(preset_name); + const char* preset_name_short = preset_name_long; + + //Which Preset have we selected? + if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok270Async")) { + preset_name_short = "AM270"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetOok650Async")) { + preset_name_short = "AM650"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev238Async")) { + preset_name_short = "FM238"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev12KAsync")) { + preset_name_short = "FM12K"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPreset2FSKDev476Async")) { + preset_name_short = "FM476"; + } else if(!strcmp(preset_name_long, "FuriHalSubGhzPresetCustom")) { + preset_name_short = "Custom"; + custom_preset = true; + } + + //Set the preset_name + furi_string_set_str(preset_name, preset_name_short); + + if(!custom_preset) { + size_t preset_index = subghz_setting_get_preset_count(app_settings); + for(size_t i = 0; i < subghz_setting_get_preset_count(app_settings); i++) { + if(!strcmp( + subghz_setting_get_preset_name(app_settings, i), preset_name_short)) { + preset_index = i; + break; + } + } + if(preset_index >= subghz_setting_get_preset_count(app_settings)) { + preset_name_short = "AM650"; + for(size_t i = 0; i < subghz_setting_get_preset_count(app_settings); i++) { + if(!strcmp( + subghz_setting_get_preset_name(app_settings, i), + preset_name_short)) { + preset_index = i; + break; + } + } + if(preset_index >= subghz_setting_get_preset_count(app_settings)) { + FURI_LOG_E(TAG, "Failed to get preset index!"); + break; + } + } + + //Get the preset data into a buffer to copy it. + uint8_t* temp_preset_data = NULL; + uint32_t temp_preset_data_size = 0; + temp_preset_data = subghz_setting_get_preset_data(app_settings, preset_index); + temp_preset_data_size = + subghz_setting_get_preset_data_size(app_settings, preset_index); + + //Error fallback + if(temp_preset_data == NULL) { + FURI_LOG_E(TAG, "Failed to get preset data!"); + break; + } else { + //Copy the preset data, so we can initialize it. + preset_data = malloc(temp_preset_data_size); + memcpy(preset_data, temp_preset_data, temp_preset_data_size); + preset_data_size = temp_preset_data_size; + + //Yay, we made it... + error = false; + } + } + } + + /* Custom_preset_data (only if present) */ + if(custom_preset && + flipper_format_get_value_count(ff, preset_data_index, &preset_data_size) && + preset_data_size > 0) { + if(preset_data_size >= 1024) { + FURI_LOG_E( + "ProtoPirate", + "%s too large: %lu", + preset_data_index, + (unsigned long)preset_data_size); + break; + } + + preset_data = malloc(preset_data_size); + if(!preset_data) { + FURI_LOG_E( + "ProtoPirate", + "Malloc failed: %s (%lu bytes)", + preset_data_index, + (unsigned long)preset_data_size); + break; + } + + flipper_format_rewind(ff); + if(!flipper_format_read_hex(ff, preset_data_index, preset_data, preset_data_size)) { + break; + } + + FURI_LOG_D(TAG, "Using Custom Preset Data from Models File..."); + + //Yay, we made it... + error = false; + } + } + } + + if(!error) { + //Set the Car Model name and index. + furi_string_set((car_model)->name, model_name); + car_model->index = index; + + //Alloc a preset if none, or recycle. + if(!car_model->preset) { + car_model->preset = malloc(sizeof(SubGhzRadioPreset)); + car_model->preset->name = furi_string_alloc(); + } else { + //Free old data.. + if(car_model->preset->data) { + free(car_model->preset->data); + car_model->preset->data = NULL; + } + } + furi_string_set(car_model->preset->name, preset_name); + car_model->preset->data = preset_data; + car_model->preset->data_size = preset_data_size; + car_model->preset->frequency = frequency; + } else { + car_model->index = 0; + furi_string_set_str( + car_model->name, + (car_models_count) ? "< Select a Car Model >" : "No Models in Database"); + + if(car_model->preset) { + if(car_model->preset->data) { + free(car_model->preset->data); + car_model->preset->data = NULL; + } + + car_model->preset->data_size = 0; + + if(car_model->preset->name) { + furi_string_free(car_model->preset->name); + car_model->preset->name = NULL; + } + + free(car_model->preset); + car_model->preset = NULL; + + // If we allocated preset_data but never assigned it, free it + if(custom_preset && preset_data_size) { + free(preset_data); + } + } + } + + //Close up and return. + flipper_format_free(ff); + furi_string_free(header); + furi_string_free(model_name); + furi_string_free(preset_name); + furi_record_close(RECORD_STORAGE); + return (!error); +} + +uint16_t car_model_get_count(void) { + uint32_t version = 0; + uint32_t model_count = 0; + + //Set up storage, init temp variables + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* ff = flipper_format_file_alloc(storage); + FuriString* header = furi_string_alloc(); + + //Open file, read header, check version, and grab the model count. + if(!flipper_format_file_open_existing(ff, PROTOPIRATE_MODELS_FILE)) { + FURI_LOG_W(TAG, "Failed to open " PROTOPIRATE_MODELS_FILE); + } else if(!flipper_format_read_header(ff, header, &version)) { + FURI_LOG_W(TAG, "Failed to read models file header."); + } else if(version != MODELS_FILE_VERSION || furi_string_cmp_str(header, MODELS_FILE_HEADER) != 0) { + FURI_LOG_W(TAG, "Models file version is invalid."); + } else if(!flipper_format_read_uint32(ff, MODELS_FIELD_MODEL_COUNT, &model_count, 1)) { + FURI_LOG_W(TAG, "Failed to read " MODELS_FIELD_MODEL_COUNT "."); + } + + //Free temp variables, close storage. + furi_string_free(header); + flipper_format_free(ff); + furi_record_close(RECORD_STORAGE); + + //Finished, return the count or 0 if error. + return (uint16_t)model_count; +} diff --git a/applications/system/ProtoPirate/helpers/protopirate_models.h b/applications/system/ProtoPirate/helpers/protopirate_models.h new file mode 100644 index 00000000..2ac69ffe --- /dev/null +++ b/applications/system/ProtoPirate/helpers/protopirate_models.h @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include "core/record.h" +#include + +typedef struct ProtoPirateCarModel { + uint16_t index; + FuriString* name; + SubGhzRadioPreset* preset; + int16_t last_preset_index; +} ProtoPirateCarModel; + +bool car_model_get_by_index( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t model_count, + SubGhzSetting* app_settings); + +uint16_t car_model_get_count(void); diff --git a/applications/system/ProtoPirate/helpers/protopirate_protocol_plugin_host.c b/applications/system/ProtoPirate/helpers/protopirate_protocol_plugin_host.c index 48f53bea..d4b2fe9f 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_protocol_plugin_host.c +++ b/applications/system/ProtoPirate/helpers/protopirate_protocol_plugin_host.c @@ -3,12 +3,12 @@ #include "../protocols/protocol_items.h" #include +#include +#include /* ProtoPirate private SubGhz API table (api/protopirate_api_table.cpp) — exposes * the SubGhz lib symbols the protocol plugins import at runtime. */ extern const ElfApiInterface* const subghz_application_api_interface; -#include -#include #define TAG "ProtoPirateProtocolPlugin" #ifdef ENABLE_EMULATE_FEATURE diff --git a/applications/system/ProtoPirate/helpers/protopirate_psa_bf_host.c b/applications/system/ProtoPirate/helpers/protopirate_psa_bf_host.c index b7f3c91b..d89ed980 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_psa_bf_host.c +++ b/applications/system/ProtoPirate/helpers/protopirate_psa_bf_host.c @@ -5,13 +5,13 @@ #include "../scenes/plugins/protopirate_psa_bf_plugin.h" #include +#include +#include +#include /* ProtoPirate private SubGhz API table (api/protopirate_api_table.cpp) — exposes * the SubGhz lib symbols the protocol plugins import at runtime. */ extern const ElfApiInterface* const subghz_application_api_interface; -#include -#include -#include #define TAG "ProtoPiratePsaBfHost" #define PSA_BF_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_psa_bf_plugin.fal") diff --git a/applications/system/ProtoPirate/helpers/protopirate_settings.c b/applications/system/ProtoPirate/helpers/protopirate_settings.c index 79bf0879..4a53313b 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_settings.c +++ b/applications/system/ProtoPirate/helpers/protopirate_settings.c @@ -17,9 +17,12 @@ void protopirate_settings_set_defaults(ProtoPirateSettings* settings) { settings->preset_index = 0; settings->tx_power = 0; settings->auto_save = false; + settings->sound = false; settings->hopping_enabled = false; settings->emulate_feature_enabled = false; settings->check_saved = false; + settings->datetime_filenames = false; + settings->car_model_index = 0; } void protopirate_settings_load(ProtoPirateSettings* settings) { @@ -118,15 +121,37 @@ void protopirate_settings_load(ProtoPirateSettings* settings) { } settings->check_saved = (check_saved_temp == 1); + uint32_t sound_temp = 0; + if(!flipper_format_read_uint32(ff, "Sound", &sound_temp, 1)) { + check_saved_temp = 0; + } + settings->sound = (sound_temp == 1); + + // Read Date/Time file names. + uint32_t datetime_filenames_temp = 0; + if(!flipper_format_read_uint32(ff, "DateTimeFilenames", &datetime_filenames_temp, 1)) { + FURI_LOG_W(TAG, "Failed to read date-time filenames, using default"); + datetime_filenames_temp = 0; + } + settings->datetime_filenames = (datetime_filenames_temp == 1); + + // Read Selected Car Model + uint32_t car_model_index_temp = 0; + if(!flipper_format_read_uint32(ff, "CarModelIndex", &car_model_index_temp, 1)) { + car_model_index_temp = 0; + } + settings->car_model_index = car_model_index_temp; + FURI_LOG_I( TAG, - "Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d", + "Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d, sound = %d", settings->frequency, settings->preset_index, settings->auto_save, settings->hopping_enabled, settings->emulate_feature_enabled, - settings->check_saved); + settings->check_saved, + settings->sound); } while(false); @@ -200,19 +225,33 @@ void protopirate_settings_save(ProtoPirateSettings* settings) { FURI_LOG_E(TAG, "Failed to write check saved"); break; } + uint32_t sound_temp = settings->sound ? 1 : 0; + if(!flipper_format_write_uint32(ff, "Sound", &sound_temp, 1)) { + FURI_LOG_E(TAG, "Failed to write Sound."); + break; + } + uint32_t datetime_filenames_temp = settings->datetime_filenames ? 1 : 0; + if(!flipper_format_write_uint32(ff, "DateTimeFilenames", &datetime_filenames_temp, 1)) { + FURI_LOG_E(TAG, "Failed to write Date Time Filenames"); + } + uint32_t car_model_index_temp = settings->car_model_index; + if(!flipper_format_write_uint32(ff, "CarModelIndex", &car_model_index_temp, 1)) { + FURI_LOG_E(TAG, "Failed to write car model"); + break; + } write_ok = true; FURI_LOG_I( TAG, - "Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d", + "Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d, check_saved=%d, sound=%d", settings->frequency, settings->preset_index, settings->auto_save, settings->hopping_enabled, settings->emulate_feature_enabled, - settings->check_saved); - + settings->check_saved, + settings->sound); } while(false); flipper_format_free(ff); diff --git a/applications/system/ProtoPirate/helpers/protopirate_settings.h b/applications/system/ProtoPirate/helpers/protopirate_settings.h index c5b3e604..b25fbaa3 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_settings.h +++ b/applications/system/ProtoPirate/helpers/protopirate_settings.h @@ -14,9 +14,12 @@ typedef struct { uint8_t preset_index; uint8_t tx_power; bool auto_save; + bool sound; bool hopping_enabled; bool emulate_feature_enabled; bool check_saved; + bool datetime_filenames; + uint16_t car_model_index; } ProtoPirateSettings; void protopirate_settings_load(ProtoPirateSettings* settings); diff --git a/applications/system/ProtoPirate/helpers/protopirate_storage.c b/applications/system/ProtoPirate/helpers/protopirate_storage.c index e6934248..6c4faa1f 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_storage.c +++ b/applications/system/ProtoPirate/helpers/protopirate_storage.c @@ -140,7 +140,10 @@ static void sanitize_filename(const char* input, char* output, size_t output_siz output[j] = '\0'; } -bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename) { +bool protopirate_storage_get_next_filename( + const char* protocol_name, + FuriString* out_filename, + bool dont_add_zero) { if(!protocol_name || !out_filename) return false; Storage* storage = furi_record_open(RECORD_STORAGE); FuriString* temp_path = furi_string_alloc(); @@ -151,13 +154,18 @@ bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString sanitize_filename(protocol_name, safe_name, sizeof(safe_name)); while(!found && index <= 999) { - furi_string_printf( - temp_path, - "%s/%s_%03lu%s", - PROTOPIRATE_APP_FOLDER, - safe_name, - (unsigned long)index, - PROTOPIRATE_APP_EXTENSION); + if(index == 0 && dont_add_zero) { + furi_string_printf( + temp_path, "%s/%s%s", PROTOPIRATE_APP_FOLDER, safe_name, PROTOPIRATE_APP_EXTENSION); + } else { + furi_string_printf( + temp_path, + "%s/%s_%03lu%s", + PROTOPIRATE_APP_FOLDER, + safe_name, + (unsigned long)index, + PROTOPIRATE_APP_EXTENSION); + } if(!storage_file_exists(storage, furi_string_get_cstr(temp_path))) { furi_string_set(out_filename, temp_path); @@ -222,7 +230,6 @@ static const char* const protopirate_storage_tail_u32_fields[] = { "Encrypted", "Decrypted", "KIAVersion", - "Checksum", }; static bool protopirate_storage_fail(const char* action, const char* key) { @@ -694,7 +701,8 @@ void protopirate_storage_delete_temp(void) { bool protopirate_storage_save_capture( FlipperFormat* flipper_format, const char* protocol_name, - FuriString* out_path) { + FuriString* out_path, + bool datetime_filenames) { furi_check(flipper_format); furi_check(protocol_name); furi_check(out_path); @@ -706,7 +714,7 @@ bool protopirate_storage_save_capture( FuriString* file_path = furi_string_alloc(); - if(!protopirate_storage_get_next_filename(protocol_name, file_path)) { + if(!protopirate_storage_get_next_filename(protocol_name, file_path, datetime_filenames)) { FURI_LOG_E(TAG, "Failed to get next filename"); furi_string_free(file_path); return false; diff --git a/applications/system/ProtoPirate/helpers/protopirate_storage.h b/applications/system/ProtoPirate/helpers/protopirate_storage.h index 7d9d6773..1dfb7266 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_storage.h +++ b/applications/system/ProtoPirate/helpers/protopirate_storage.h @@ -24,7 +24,8 @@ bool protopirate_storage_commit_temp_file( bool protopirate_storage_save_capture( FlipperFormat* flipper_format, const char* protocol_name, - FuriString* out_path); + FuriString* out_path, + bool datetime_filenames); // Save a capture to a specific file path (user-chosen name) bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path); @@ -33,7 +34,10 @@ bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, con void protopirate_storage_delete_temp(void); // Get next available filename for a protocol -bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename); +bool protopirate_storage_get_next_filename( + const char* protocol_name, + FuriString* out_filename, + bool dont_add_zero); bool protopirate_storage_get_capture_display_protocol( FlipperFormat* flipper_format, diff --git a/applications/system/ProtoPirate/helpers/protopirate_tool_scene_host.c b/applications/system/ProtoPirate/helpers/protopirate_tool_scene_host.c index 4ba0ba14..278a49ac 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_tool_scene_host.c +++ b/applications/system/ProtoPirate/helpers/protopirate_tool_scene_host.c @@ -3,11 +3,11 @@ #include "radio_device_loader.h" #include +#include /* ProtoPirate private SubGhz API table (api/protopirate_api_table.cpp) — exposes * the SubGhz lib symbols the protocol plugins import at runtime. */ extern const ElfApiInterface* const subghz_application_api_interface; -#include #define TAG "ProtoPirateToolScene" diff --git a/applications/system/ProtoPirate/helpers/protopirate_txrx.c b/applications/system/ProtoPirate/helpers/protopirate_txrx.c index afe44805..f2f81d1a 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_txrx.c +++ b/applications/system/ProtoPirate/helpers/protopirate_txrx.c @@ -133,6 +133,13 @@ uint32_t protopirate_rx(ProtoPirateApp* app, uint32_t frequency) { subghz_devices_idle(app->txrx->radio_device); uint32_t value = subghz_devices_set_frequency(app->txrx->radio_device, frequency); subghz_devices_flush_rx(app->txrx->radio_device); + + //Enable the Speaker so I can hear the keys. + if(app->sound) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(1000)) { + app->txrx->radio_device->interconnect->set_async_mirror_pin(&gpio_speaker); + } + } subghz_devices_set_rx(app->txrx->radio_device); subghz_devices_start_async_rx( @@ -169,6 +176,13 @@ void protopirate_rx_end(ProtoPirateApp* app) { subghz_devices_idle(app->txrx->radio_device); } + if(app->sound) { + if(furi_hal_speaker_is_mine()) { + subghz_devices_set_async_mirror_pin(app->txrx->radio_device, NULL); + furi_hal_speaker_release(); + } + } + app->txrx->txrx_state = ProtoPirateTxRxStateIDLE; } diff --git a/applications/system/ProtoPirate/helpers/protopirate_types.h b/applications/system/ProtoPirate/helpers/protopirate_types.h index 64145ae2..b04acb84 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_types.h +++ b/applications/system/ProtoPirate/helpers/protopirate_types.h @@ -44,6 +44,7 @@ typedef enum { ProtoPirateCustomEventSubDecodeEmulate, ProtoPirateCustomEventSubDecodeBruteforceStart, ProtoPirateCustomEventPsaBruteforceComplete, + ProtoPirateCustomEventSubDecodeSaveConfirm, // File Browser ProtoPirateCustomEventSavedFileSelected, // Need saving confirmation diff --git a/applications/system/ProtoPirate/helpers/protopirate_views.c b/applications/system/ProtoPirate/helpers/protopirate_views.c index 00eae799..fc2b0a34 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_views.c +++ b/applications/system/ProtoPirate/helpers/protopirate_views.c @@ -5,24 +5,6 @@ #define TAG "ProtoPirateViews" -bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) { - furi_check(app); - if(app->variable_item_list) { - return true; - } - - app->variable_item_list = variable_item_list_alloc(); - if(!app->variable_item_list) { - return false; - } - - view_dispatcher_add_view( - app->view_dispatcher, - ProtoPirateViewVariableItemList, - variable_item_list_get_view(app->variable_item_list)); - return true; -} - bool protopirate_ensure_widget(ProtoPirateApp* app) { furi_check(app); if(app->widget) { @@ -101,7 +83,6 @@ void protopirate_views_free(ProtoPirateApp* app) { if(app->variable_item_list) { FURI_LOG_D(TAG, "Removing variable_item_list view"); view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); - variable_item_list_free(app->variable_item_list); app->variable_item_list = NULL; } @@ -133,3 +114,11 @@ void protopirate_views_free(ProtoPirateApp* app) { app->protopirate_receiver = NULL; } } + +void protopirate_variable_item_list_free(ProtoPirateApp* app) { + if(app->variable_item_list) { + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + variable_item_list_free(app->variable_item_list); + app->variable_item_list = NULL; + } +} diff --git a/applications/system/ProtoPirate/helpers/protopirate_views.h b/applications/system/ProtoPirate/helpers/protopirate_views.h index 63247ec1..4c227a6a 100644 --- a/applications/system/ProtoPirate/helpers/protopirate_views.h +++ b/applications/system/ProtoPirate/helpers/protopirate_views.h @@ -10,3 +10,4 @@ bool protopirate_ensure_text_input(ProtoPirateApp* app); bool protopirate_ensure_view_about(ProtoPirateApp* app); bool protopirate_ensure_receiver_view(ProtoPirateApp* app); void protopirate_views_free(ProtoPirateApp* app); +void protopirate_variable_item_list_free(ProtoPirateApp* app); diff --git a/applications/system/ProtoPirate/helpers/variable_item_list.c b/applications/system/ProtoPirate/helpers/variable_item_list.c new file mode 100644 index 00000000..97d90896 --- /dev/null +++ b/applications/system/ProtoPirate/helpers/variable_item_list.c @@ -0,0 +1,688 @@ +#include "helpers/variable_item_list.h" +#include +#include +#include +#include +#include + +/**************************************************************************************************/ +/* ICON ASSETS */ +/**************************************************************************************************/ +struct Icon { + const uint16_t width; + const uint16_t height; + const uint8_t frame_count; + const uint8_t frame_rate; + const uint8_t* const* frames; +}; + +const uint8_t _I_WarningDolphin_45x42e_0[] = { + 0x01, 0x00, 0xc6, 0x00, 0x00, 0x1c, 0x22, 0x04, 0x05, 0x7f, 0xfc, 0x1e, 0x20, 0x05, 0x1e, 0x04, + 0x02, 0x30, 0x05, 0x29, 0x84, 0x02, 0xc1, 0x20, 0x02, 0x8c, 0x22, 0x01, 0x80, 0x02, 0x94, 0x10, + 0x32, 0x30, 0x10, 0x10, 0x87, 0xca, 0x84, 0x03, 0x10, 0x42, 0x81, 0x48, 0x28, 0x38, 0x08, 0x04, + 0x3e, 0x01, 0x84, 0x83, 0xe0, 0x30, 0x11, 0x08, 0x05, 0xa2, 0x11, 0x40, 0xa0, 0x4b, 0xc6, 0xc5, + 0x40, 0xd0, 0x56, 0xe0, 0x10, 0x60, 0x29, 0x54, 0xf0, 0x10, 0x18, 0xf0, 0x14, 0x6b, 0xf6, 0x0c, + 0x04, 0x3e, 0x40, 0x05, 0x12, 0x80, 0xc1, 0xe4, 0x01, 0xd2, 0xf8, 0x40, 0xe4, 0x18, 0x09, 0xf4, + 0x03, 0xf1, 0x01, 0x90, 0x40, 0x28, 0x30, 0x0f, 0xe4, 0x00, 0x16, 0x24, 0x11, 0xbf, 0x01, 0x44, + 0xee, 0x53, 0xf0, 0x29, 0xf0, 0x3e, 0x02, 0x91, 0x3b, 0x8c, 0xc3, 0x81, 0x13, 0x90, 0x48, 0x20, + 0x3f, 0xf9, 0xfc, 0x42, 0x60, 0x05, 0x10, 0x98, 0x81, 0x56, 0x11, 0x38, 0x02, 0x9c, 0x1a, 0x31, + 0x1e, 0x02, 0x8f, 0x02, 0x03, 0x1c, 0x90, 0xc0, 0x7c, 0x02, 0xf1, 0xce, 0x02, 0x07, 0x01, 0x1f, + 0x80, 0x63, 0xa8, 0x08, 0x71, 0x3c, 0x8e, 0x39, 0x24, 0x40, 0x51, 0xc7, 0x81, 0x53, 0x0f, 0x3c, + 0x02, 0x9d, 0x1e, 0x38, 0x29, 0x10, 0x29, 0x17, 0xc8, 0x0a, 0x32, 0x3a, 0x00, 0x14, 0x4b, 0xa2, + 0x05, 0x58, 0x98, 0x15, 0x22, 0x20, 0x54, 0x84, 0x81, 0x50, +}; +const uint8_t* const _I_WarningDolphin_45x42e[] = {_I_WarningDolphin_45x42e_0}; + +const Icon I_WarningDolphin_45x42e = { + .width = 45, + .height = 42, + .frame_count = 1, + .frame_rate = 0, + .frames = _I_WarningDolphin_45x42e}; + +const uint8_t _I_Lock_7x8e_0[] = { + 0x00, + 0x1c, + 0x22, + 0x22, + 0x7f, + 0x7f, + 0x77, + 0x7f, + 0x3e, +}; +const uint8_t* const _I_Lock_7x8e[] = {_I_Lock_7x8e_0}; + +const Icon I_Lock_7x8e = + {.width = 7, .height = 8, .frame_count = 1, .frame_rate = 0, .frames = _I_Lock_7x8e}; + +/**************************************************************************************************/ + +struct VariableItem { + FuriString* label; + uint8_t current_value_index; + FuriString* current_value_text; + uint8_t values_count; + VariableItemChangeCallback change_callback; + void* context; + + bool locked; + FuriString* locked_message; +}; + +ARRAY_DEF(VariableItemArray, VariableItem, M_POD_OPLIST); //-V658 + +struct VariableItemList { + View* view; + VariableItemListEnterCallback callback; + void* context; + + FuriTimer* scroll_timer; + FuriTimer* locked_timer; +}; + +typedef struct { + VariableItemArray_t items; + uint8_t position; + uint8_t window_position; + + FuriString* header; + size_t scroll_counter; + bool locked_message_visible; +} VariableItemListModel; + +static void variable_item_list_process_up(VariableItemList* variable_item_list); +static void variable_item_list_process_down(VariableItemList* variable_item_list); +static void variable_item_list_process_left(VariableItemList* variable_item_list); +static void variable_item_list_process_right(VariableItemList* variable_item_list); +static void variable_item_list_process_ok(VariableItemList* variable_item_list); + +static size_t variable_item_list_items_on_screen(VariableItemListModel* model) { + size_t res = 4; + return (furi_string_empty(model->header)) ? res : res - 1; +} + +static void variable_item_list_draw_callback(Canvas* canvas, void* _model) { + VariableItemListModel* model = _model; + + const uint8_t item_height = 16; + uint8_t item_width = canvas_width(canvas) - 5; + + canvas_clear(canvas); + + if(!furi_string_empty(model->header)) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 4, 11, furi_string_get_cstr(model->header)); + } + + uint8_t position = 0; + VariableItemArray_it_t it; + + canvas_set_font(canvas, FontSecondary); + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + uint8_t item_position = position - model->window_position; + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + uint8_t y_offset = furi_string_empty(model->header) ? 0 : item_height; + + if(item_position < items_on_screen) { + const VariableItem* item = VariableItemArray_cref(it); + uint8_t item_y = y_offset + (item_position * item_height); + uint8_t item_text_y = item_y + item_height - 4; + size_t scroll_counter = 0; + + if(position == model->position) { + canvas_set_color(canvas, ColorBlack); + elements_slightly_rounded_box(canvas, 0, item_y + 1, item_width, item_height - 2); + canvas_set_color(canvas, ColorWhite); + scroll_counter = model->scroll_counter; + if(scroll_counter < 1) { // Show text beginning a little longer + scroll_counter = 0; + } else { + scroll_counter -= 1; + } + } else { + canvas_set_color(canvas, ColorBlack); + } + + uint8_t value_pos_x = 73; + uint8_t label_width = 66; + if(item->locked) { + // Span label up to lock icon + value_pos_x = 110; + label_width = 100; + } else if(item->current_value_index == 0 && furi_string_empty(item->current_value_text)) { + // Only label text, no value text, show longer label + label_width = 109; + } else if(furi_string_size(item->current_value_text) < 4U) { + // Smaller value section for short values + value_pos_x = 80; + label_width = 71; + } + + elements_scrollable_text_line( + canvas, + 6, + item_text_y, + label_width, + item->label, + scroll_counter, + (position != model->position)); + + if(item->locked) { + canvas_draw_icon(canvas, value_pos_x, item_text_y - 8, &I_Lock_7x8e); + } else { + if(item->current_value_index > 0) { + canvas_draw_str(canvas, value_pos_x, item_text_y, "<"); + } + + elements_scrollable_text_line( + canvas, + value_pos_x + 6, + item_text_y, + 109 - (value_pos_x), + item->current_value_text, + scroll_counter, + false); + + if(item->current_value_index < (item->values_count - 1)) { + canvas_draw_str(canvas, 115, item_text_y, ">"); + } + } + } + + position++; + } + + elements_scrollbar(canvas, model->position, VariableItemArray_size(model->items)); + + if(model->locked_message_visible) { + //if(momentum_settings.popup_overlay) { + // canvas_draw_overlay(canvas); + //} + canvas_set_color(canvas, ColorWhite); + canvas_draw_box(canvas, 8, 10, 110, 48); + canvas_set_color(canvas, ColorBlack); + canvas_draw_icon(canvas, 10, 14, &I_WarningDolphin_45x42e); + canvas_draw_rframe(canvas, 8, 8, 112, 50, 3); + canvas_draw_rframe(canvas, 9, 9, 110, 48, 2); + elements_multiline_text_aligned( + canvas, + 84, + 32, + AlignCenter, + AlignCenter, + furi_string_get_cstr( + VariableItemArray_get(model->items, model->position)->locked_message)); + } +} + +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index) { + furi_check(variable_item_list); + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t position = index; + const size_t items_count = VariableItemArray_size(model->items); + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + + if(position >= items_count) { + position = 0; + } + + model->position = position; + model->window_position = position; + + if(model->window_position > 0) { + model->window_position -= 1; + } + + if(items_count <= items_on_screen) { + model->window_position = 0; + } else { + const size_t pos = items_count - items_on_screen; + if(model->window_position > pos) { + model->window_position = pos; + } + } + }, + true); +} + +/* uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + VariableItemListModel* model = view_get_model(variable_item_list->view); + uint8_t idx = model->position; + view_commit_model(variable_item_list->view, false); + return idx; +}*/ + +/* void variable_item_list_set_header(VariableItemList* variable_item_list, const char* header) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + if(header == NULL) { + furi_string_reset(model->header); + } else { + furi_string_set_str(model->header, header); + } + }, + true); +}*/ + +static bool variable_item_list_input_callback(InputEvent* event, void* context) { + VariableItemList* variable_item_list = context; + furi_assert(variable_item_list); + bool consumed = false; + + bool locked_message_visible = false; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { locked_message_visible = model->locked_message_visible; }, + false); + + if((event->type != InputTypePress && event->type != InputTypeRelease) && + locked_message_visible) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); + consumed = true; + } else if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyUp: + consumed = true; + variable_item_list_process_up(variable_item_list); + break; + case InputKeyDown: + consumed = true; + variable_item_list_process_down(variable_item_list); + break; + case InputKeyLeft: + consumed = true; + variable_item_list_process_left(variable_item_list); + break; + case InputKeyRight: + consumed = true; + variable_item_list_process_right(variable_item_list); + break; + case InputKeyOk: + variable_item_list_process_ok(variable_item_list); + break; + default: + break; + } + } else if(event->type == InputTypeRepeat) { + switch(event->key) { + case InputKeyUp: + consumed = true; + variable_item_list_process_up(variable_item_list); + break; + case InputKeyDown: + consumed = true; + variable_item_list_process_down(variable_item_list); + break; + case InputKeyLeft: + consumed = true; + variable_item_list_process_left(variable_item_list); + break; + case InputKeyRight: + consumed = true; + variable_item_list_process_right(variable_item_list); + break; + default: + break; + } + } + + return consumed; +} + +void variable_item_list_process_up(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + if(model->position > 0) { + model->position--; + + if((model->position == model->window_position) && (model->window_position > 0)) { + model->window_position--; + } + } else { + model->position = VariableItemArray_size(model->items) - 1; + if(model->position > (items_on_screen - 1)) { + model->window_position = model->position - (items_on_screen - 1); + } + } + model->scroll_counter = 0; + }, + true); +} + +void variable_item_list_process_down(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + uint8_t items_on_screen = variable_item_list_items_on_screen(model); + if(model->position < (VariableItemArray_size(model->items) - 1)) { + model->position++; + if((model->position - model->window_position) > (items_on_screen - 2) && + model->window_position < + (VariableItemArray_size(model->items) - items_on_screen)) { + model->window_position++; + } + } else { + model->position = 0; + model->window_position = 0; + } + model->scroll_counter = 0; + }, + true); +} + +VariableItem* variable_item_list_get_selected_item(VariableItemListModel* model) { + VariableItem* item = NULL; + + VariableItemArray_it_t it; + uint8_t position = 0; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + if(position == model->position) { + break; + } + position++; + } + + item = VariableItemArray_ref(it); + + furi_assert(item); + return item; +} + +void variable_item_list_process_left(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(!item->values_count) { + //No List to change. Send VariableItemListEventCycleLeft to the handler as current value index + //This way no breaking API changes are made. + item->current_value_index = VariableItemListEventCycleLeft; + if(item->change_callback) { + item->change_callback(item); + } + } else if(item->current_value_index > 0) { + item->current_value_index--; + model->scroll_counter = 0; + if(item->change_callback) { + item->change_callback(item); + } + } + }, + true); +} + +void variable_item_list_process_right(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(!item->values_count) { + //No List to change. Send VariableItemListEventCycleLeft to the handler as current value index + //This way no breaking API changes are made. + item->current_value_index = VariableItemListEventCycleRight; + if(item->change_callback) { + item->change_callback(item); + } + } else if(item->current_value_index < (item->values_count - 1)) { + item->current_value_index++; + model->scroll_counter = 0; + if(item->change_callback) { + item->change_callback(item); + } + } + }, + true); +} + +void variable_item_list_process_ok(VariableItemList* variable_item_list) { + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItem* item = variable_item_list_get_selected_item(model); + if(item->locked) { + model->locked_message_visible = true; + furi_timer_start( + variable_item_list->locked_timer, furi_kernel_get_tick_frequency() * 3); + } else if(variable_item_list->callback) { + variable_item_list->callback(variable_item_list->context, model->position); + } + }, + true); +} + +static void variable_item_list_scroll_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->scroll_counter++; }, + true); +} + +void variable_item_list_locked_timer_callback(void* context) { + furi_assert(context); + VariableItemList* variable_item_list = context; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { model->locked_message_visible = false; }, + true); +} + +VariableItemList* variable_item_list_alloc(void) { + VariableItemList* variable_item_list = malloc(sizeof(VariableItemList)); + variable_item_list->view = view_alloc(); + view_set_context(variable_item_list->view, variable_item_list); + view_allocate_model( + variable_item_list->view, ViewModelTypeLocking, sizeof(VariableItemListModel)); + view_set_draw_callback(variable_item_list->view, variable_item_list_draw_callback); + view_set_input_callback(variable_item_list->view, variable_item_list_input_callback); + + variable_item_list->locked_timer = furi_timer_alloc( + variable_item_list_locked_timer_callback, FuriTimerTypeOnce, variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItemArray_init(model->items); + model->position = 0; + model->window_position = 0; + model->header = furi_string_alloc(); + model->scroll_counter = 0; + }, + true); + variable_item_list->scroll_timer = furi_timer_alloc( + variable_item_list_scroll_timer_callback, FuriTimerTypePeriodic, variable_item_list); + furi_timer_start(variable_item_list->scroll_timer, 333); + + return variable_item_list; +} + +void variable_item_list_free(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + furi_string_free(model->header); + VariableItemArray_it_t it; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + furi_string_free(VariableItemArray_ref(it)->label); + furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); + } + VariableItemArray_clear(model->items); + }, + false); + furi_timer_stop(variable_item_list->scroll_timer); + furi_timer_free(variable_item_list->scroll_timer); + furi_timer_stop(variable_item_list->locked_timer); + furi_timer_free(variable_item_list->locked_timer); + view_free(variable_item_list->view); + free(variable_item_list); + variable_item_list = NULL; +} + +void variable_item_list_reset(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + VariableItemArray_it_t it; + for(VariableItemArray_it(it, model->items); !VariableItemArray_end_p(it); + VariableItemArray_next(it)) { + furi_string_free(VariableItemArray_ref(it)->label); + furi_string_free(VariableItemArray_ref(it)->current_value_text); + furi_string_free(VariableItemArray_ref(it)->locked_message); + } + VariableItemArray_reset(model->items); + furi_string_reset(model->header); + }, + false); +} + +View* variable_item_list_get_view(VariableItemList* variable_item_list) { + furi_check(variable_item_list); + return variable_item_list->view; +} + +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context) { + VariableItem* item = NULL; + furi_check(label); + furi_check(variable_item_list); + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + item = VariableItemArray_push_new(model->items); + item->label = furi_string_alloc_set(label); + item->values_count = values_count; + item->change_callback = change_callback; + item->context = context; + item->current_value_index = 0; + item->current_value_text = furi_string_alloc(); + item->locked = false; + item->locked_message = furi_string_alloc(); + }, + true); + + return item; +} + +VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position) { + furi_check(variable_item_list); + VariableItem* item = NULL; + + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + if(position < VariableItemArray_size(model->items)) { + item = VariableItemArray_get(model->items, position); + } + }, + true); + + return item; +} + +void variable_item_list_set_enter_callback( + VariableItemList* variable_item_list, + VariableItemListEnterCallback callback, + void* context) { + furi_check(callback); + with_view_model( + variable_item_list->view, + VariableItemListModel * model, + { + UNUSED(model); + variable_item_list->callback = callback; + variable_item_list->context = context; + }, + false); +} + +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index) { + furi_check(item); + item->current_value_index = current_value_index; +} + +/*void variable_item_set_values_count(VariableItem* item, uint8_t values_count) { + furi_check(item); + item->values_count = values_count; +}*/ + +void variable_item_set_item_label(VariableItem* item, const char* label) { + furi_check(item); + furi_check(label); + furi_string_set(item->label, label); +} + +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text) { + furi_check(item); + furi_string_set(item->current_value_text, current_value_text); +} + +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message) { + furi_check(item); + item->locked = locked; + if(locked_message) { + furi_string_set(item->locked_message, locked_message); + } else if(locked && furi_string_empty(item->locked_message)) { + furi_string_set(item->locked_message, "Locked!"); + } +} + +uint8_t variable_item_get_current_value_index(VariableItem* item) { + furi_check(item); + return item->current_value_index; +} + +void* variable_item_get_context(VariableItem* item) { + furi_check(item); + return item->context; +} diff --git a/applications/system/ProtoPirate/helpers/variable_item_list.h b/applications/system/ProtoPirate/helpers/variable_item_list.h new file mode 100644 index 00000000..6fa4423f --- /dev/null +++ b/applications/system/ProtoPirate/helpers/variable_item_list.h @@ -0,0 +1,159 @@ +/** + * @file variable_item_list.h + * GUI: VariableItemList view module API + */ + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//For sending Left and Right events up to app +//When there are ZERO values (no cycling used by list itself) +//This allows easy use of lists that cycle the whole line, +//And use more than 255 values. +typedef enum { + VariableItemListEventCycleReset, + VariableItemListEventCycleLeft, + VariableItemListEventCycleRight, +} VariableItemListEvents; + +typedef struct VariableItemList VariableItemList; +typedef struct VariableItem VariableItem; +typedef void (*VariableItemChangeCallback)(VariableItem* item); +typedef void (*VariableItemListEnterCallback)(void* context, uint32_t index); + +/** Allocate and initialize VariableItemList + * + * @return VariableItemList* + */ +VariableItemList* variable_item_list_alloc(void); + +/** Deinitialize and free VariableItemList + * + * @param variable_item_list VariableItemList instance + */ +void variable_item_list_free(VariableItemList* variable_item_list); + +/** Clear all elements from list + * + * @param variable_item_list VariableItemList instance + */ +void variable_item_list_reset(VariableItemList* variable_item_list); + +/** Get VariableItemList View instance + * + * @param variable_item_list VariableItemList instance + * + * @return View instance + */ +View* variable_item_list_get_view(VariableItemList* variable_item_list); + +/** Add item to VariableItemList + * + * @param variable_item_list VariableItemList instance + * @param label item name + * @param values_count item values count + * @param change_callback called on value change in gui + * @param context item context + * + * @return VariableItem* item instance + */ +VariableItem* variable_item_list_add( + VariableItemList* variable_item_list, + const char* label, + uint8_t values_count, + VariableItemChangeCallback change_callback, + void* context); + +/** Get pre-existing item instance in VariableItemList + * + * @param variable_item_list VariableItemList instance + * @param position index of the item instance to get + * + * @return VariableItem* item instance + */ +VariableItem* variable_item_list_get(VariableItemList* variable_item_list, uint8_t position); + +/** Set enter callback + * + * @param variable_item_list VariableItemList instance + * @param callback VariableItemListEnterCallback instance + * @param context pointer to context + */ +void variable_item_list_set_enter_callback( + VariableItemList* variable_item_list, + VariableItemListEnterCallback callback, + void* context); + +void variable_item_list_set_selected_item(VariableItemList* variable_item_list, uint8_t index); + +//uint8_t variable_item_list_get_selected_item_index(VariableItemList* variable_item_list); + +/** Set optional header for variable item list + * Must be called before adding items OR after adding items and before set_selected_item() + * + * @param variable_item_list VariableItemList instance + * @param header header to set + */ +// void variable_item_list_set_header(VariableItemList* variable_item_list, const char* header); + +/** Set item current selected index + * + * @param item VariableItem* instance + * @param current_value_index The current value index + */ +void variable_item_set_current_value_index(VariableItem* item, uint8_t current_value_index); + +/** Set number of values for item + * + * @param item VariableItem* instance + * @param values_count The new values count + */ +//void variable_item_set_values_count(VariableItem* item, uint8_t values_count); + +/** Set new label for item + * + * @param item VariableItem* instance + * @param label The new label text + */ +void variable_item_set_item_label(VariableItem* item, const char* label); + +/** Set item current selected text + * + * @param item VariableItem* instance + * @param current_value_text The current value text + */ +void variable_item_set_current_value_text(VariableItem* item, const char* current_value_text); + +/** Set item locked state and text + * Locked message is saved, you can set_locked(item, false, "Message") and then set_locked(item, true, NULL), it will use previous locked message + * + * @param item VariableItem* instance + * @param locked Is item locked boolean + * @param locked_message The locked message text + */ +void variable_item_set_locked(VariableItem* item, bool locked, const char* locked_message); + +/** Get item current selected index + * + * @param item VariableItem* instance + * + * @return uint8_t current selected index + */ +uint8_t variable_item_get_current_value_index(VariableItem* item); + +/** Get item context + * + * @param item VariableItem* instance + * + * @return void* item context + */ +void* variable_item_get_context(VariableItem* item); + +#ifdef __cplusplus +} +#endif diff --git a/applications/system/ProtoPirate/keystore/models.txt b/applications/system/ProtoPirate/keystore/models.txt new file mode 100644 index 00000000..ba5a1134 --- /dev/null +++ b/applications/system/ProtoPirate/keystore/models.txt @@ -0,0 +1,33 @@ +Filetype: ProtoPirate Car Models Database +Version: 2 + +ModelCount: 7 + +ModelName1: 433.92 Mhz AM650 +Frequency1: 433920000 +PresetName1: FuriHalSubGhzPresetOok650Async + +ModelName2: 433.92 Mhz FM476 +Frequency2: 433920000 +PresetName2: FuriHalSubGhzPreset2FSKDev476Async + +ModelName3: 315 Mhz AM650 +Frequency3: 315000000 +PresetName3: FuriHalSubGhzPresetOok650Async + +ModelName4: 315 Mhz FM476 +Frequency4: 315000000 +PresetName4: FuriHalSubGhzPreset2FSKDev476Async + +ModelName5: Ford Falcon BA-FGX +Frequency5: 433920000 +PresetName5: FuriHalSubGhzPresetOok650Async + +ModelName6: Ford Territory SX-SYII +Frequency6: 433920000 +PresetName6: FuriHalSubGhzPresetOok650Async + +ModelName7: Example With Honda Preset +Frequency7: 433920000 +PresetName7: FuriHalSubGhzPresetCustom +CustomPresetData7: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00 diff --git a/applications/system/ProtoPirate/protocols/fiat_v1.c b/applications/system/ProtoPirate/protocols/fiat_v1.c index f3bd3854..815acf1c 100644 --- a/applications/system/ProtoPirate/protocols/fiat_v1.c +++ b/applications/system/ProtoPirate/protocols/fiat_v1.c @@ -301,11 +301,10 @@ static void fiat_v1_decode_fields(SubGhzProtocolDecoderFiatV1* instance) { fiat_v1_verify_hitag2_key(instance); } -static bool - fiat_v1_commit( - SubGhzProtocolDecoderFiatV1* instance, - const uint8_t raw[FIAT_V1_WIRE_BYTES], - uint8_t te_variant) { +static bool fiat_v1_commit( + SubGhzProtocolDecoderFiatV1* instance, + const uint8_t raw[FIAT_V1_WIRE_BYTES], + uint8_t te_variant) { if(!fiat_v1_frame_valid(raw)) { return false; } diff --git a/applications/system/ProtoPirate/protocols/ford_v0.c b/applications/system/ProtoPirate/protocols/ford_v0.c index 6eb54163..39f37e8e 100644 --- a/applications/system/ProtoPirate/protocols/ford_v0.c +++ b/applications/system/ProtoPirate/protocols/ford_v0.c @@ -833,9 +833,9 @@ void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* outpu "Key2: %04X" " Sn: %08lX\r\n" "Cnt: %05lX" - " Checksum: %02X" - " CRC: %02X\r\n" - " Btn: %02X - %s\r\n", + " Checksum: %02X\r\n" + "Btn: %02X - %s" + " CRC: %02X", instance->generic.protocol_name, instance->generic.data_count_bit, crc_ok ? "OK" : "BAD", @@ -846,7 +846,7 @@ void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* outpu (unsigned long)instance->count, (instance->key2 >> 8) & 0xFF, - instance->key2 & 0xFF, instance->button, - button_name); + button_name, + instance->key2 & 0xFF); } diff --git a/applications/system/ProtoPirate/protocols/kia_v0.c b/applications/system/ProtoPirate/protocols/kia_v0.c index b00f9374..7c512909 100644 --- a/applications/system/ProtoPirate/protocols/kia_v0.c +++ b/applications/system/ProtoPirate/protocols/kia_v0.c @@ -29,7 +29,7 @@ static const SubGhzBlockConst kia_protocol_v0_const = { #define KIA_V0_SUZUKI_GAP 2000U #define KIA_V0_SUZUKI_GAP_SPAN 500U -#define KIA_V0_TYPE1_SYNC 750U +#define KIA_V0_TYPE1_SYNC 750U #define KIA_V0_TYPE1_PREAMBLE_PAIRS 0x13FU #define KIA_V0_TYPE2_PREAMBLE_PAIRS 0x140U #define KIA_V0_TAIL_PREAMBLE_PAIRS 0x0FU @@ -37,7 +37,7 @@ static const SubGhzBlockConst kia_protocol_v0_const = { #define KIA_V0_MITSU_PREAMBLE_MIN 72U #define KIA_V0_MITSU_PREAMBLE_MAX 88U -#define KIA_V0_UPLOAD_CAPACITY \ +#define KIA_V0_UPLOAD_CAPACITY \ ((KIA_V0_TYPE2_PREAMBLE_PAIRS * 2U) + 2U + (KIA_V0_BIT_COUNT_SUZUKI * 2U) + 3U + \ (KIA_V0_MITSU_TAIL_PREAMBLE_PAIRS * 2U) + (KIA_V0_BIT_COUNT_SUZUKI * 2U) + 4U) _Static_assert( diff --git a/applications/system/ProtoPirate/protocols/renault_v0.c b/applications/system/ProtoPirate/protocols/renault_v0.c index 2abb3f19..a219dd38 100644 --- a/applications/system/ProtoPirate/protocols/renault_v0.c +++ b/applications/system/ProtoPirate/protocols/renault_v0.c @@ -9,16 +9,16 @@ _Static_assert( RENAULT_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, "RENAULT_V0_UPLOAD_CAPACITY exceeds shared upload slab"); -#define RENAULT_V0_TE_SHORT_US 0x7DU -#define RENAULT_V0_TE_LONG_US 0xFAU -#define RENAULT_V0_TE_DELTA_US 0x45U -#define RENAULT_V0_PREAMBLE_PAIRS 16U -#define RENAULT_V0_BURST_COUNT 3U -#define RENAULT_V0_INTER_BURST_US 0x61A8U -#define RENAULT_V0_FINAL_LOW_US 250U -#define RENAULT_V0_SYNC_HIGH_US 1000U -#define RENAULT_V0_REPEAT 1U -#define RENAULT_V0_KEY2_FIELD "Key2" +#define RENAULT_V0_TE_SHORT_US 0x7DU +#define RENAULT_V0_TE_LONG_US 0xFAU +#define RENAULT_V0_TE_DELTA_US 0x45U +#define RENAULT_V0_PREAMBLE_PAIRS 16U +#define RENAULT_V0_BURST_COUNT 3U +#define RENAULT_V0_INTER_BURST_US 0x61A8U +#define RENAULT_V0_FINAL_LOW_US 250U +#define RENAULT_V0_SYNC_HIGH_US 1000U +#define RENAULT_V0_REPEAT 1U +#define RENAULT_V0_KEY2_FIELD "Key2" typedef enum { RenaultV0DecoderStepReset = 0, @@ -268,8 +268,8 @@ static bool renault_v0_emit( bool level, uint32_t duration) { const size_t prev = *index; - *index = pp_emit_merge( - instance->encoder.upload, prev, RENAULT_V0_UPLOAD_CAPACITY, level, duration); + *index = + pp_emit_merge(instance->encoder.upload, prev, RENAULT_V0_UPLOAD_CAPACITY, level, duration); if(*index > prev) { return true; } diff --git a/applications/system/ProtoPirate/protocols/renault_v1.c b/applications/system/ProtoPirate/protocols/renault_v1.c index 1e035877..a297a50c 100644 --- a/applications/system/ProtoPirate/protocols/renault_v1.c +++ b/applications/system/ProtoPirate/protocols/renault_v1.c @@ -7,14 +7,14 @@ #define TAG "RenaultV1Protocol" -#define HITAG2_TE_US 125U -#define HITAG2_HEADER_LOW_US 1500U -#define HITAG2_HEADER_HIGH_US 1000U -#define HITAG2_SHORT_GAP_US 21500U -#define HITAG2_PREAMBLE_PAIRS 250U -#define HITAG2_LONG_FRAMES 3U -#define HITAG2_SHORT_FRAMES 3U -#define HITAG2_UPLOAD_CAPACITY 1295U +#define HITAG2_TE_US 125U +#define HITAG2_HEADER_LOW_US 1500U +#define HITAG2_HEADER_HIGH_US 1000U +#define HITAG2_SHORT_GAP_US 21500U +#define HITAG2_PREAMBLE_PAIRS 250U +#define HITAG2_LONG_FRAMES 3U +#define HITAG2_SHORT_FRAMES 3U +#define HITAG2_UPLOAD_CAPACITY 1295U _Static_assert( HITAG2_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY, "HITAG2_UPLOAD_CAPACITY exceeds shared upload slab"); @@ -65,6 +65,8 @@ struct SubGhzProtocolDecoderRenaultV1 { uint64_t last_data; uint64_t last_data_2; bool last_frame_valid; + uint32_t seed; + uint64_t data_2; }; #if PROTOPIRATE_WITH_ENCODER @@ -74,6 +76,8 @@ struct SubGhzProtocolEncoderRenaultV1 { SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; + uint32_t seed; + uint64_t data_2; uint8_t recovered; uint8_t hitag2_key[6]; uint8_t tail_bits; @@ -87,7 +91,6 @@ typedef enum { RenaultV1DecoderStepData = 3, } RenaultV1DecoderStep; - const SubGhzProtocolDecoder renault_v1_decoder = { .alloc = subghz_protocol_decoder_renault_v1_alloc, .free = pp_decoder_free_default, @@ -213,11 +216,8 @@ static uint64_t hitag2_key_to_u64(const uint8_t key[6]) { return key64; } -static uint32_t hitag2_authenticator( - uint32_t uid, - uint8_t button, - uint32_t counter, - const uint8_t key[6]) { +static uint32_t + hitag2_authenticator(uint32_t uid, uint8_t button, uint32_t counter, const uint8_t key[6]) { const uint64_t key64 = hitag2_key_to_u64(key); const uint32_t nonce = (counter << 4U) | ((uint32_t)button & 0x0FU); uint64_t state = 0ULL; @@ -395,15 +395,16 @@ static void hitag2_serial_permute(const uint8_t serial[4], uint8_t perm[6]) { perm[1] = acc; const uint8_t sn0_shr3 = (uint8_t)(sn0 >> 3); - acc = (uint8_t)(hitag2_extract_bits(sn0, 2, 1) | (sn0_shr3 & 2U) | ((~(uint32_t)(sn3 >> 2)) & 4U) | - (sn1 & 0x10U)); + acc = (uint8_t)(hitag2_extract_bits(sn0, 2, 1) | (sn0_shr3 & 2U) | + ((~(uint32_t)(sn3 >> 2)) & 4U) | (sn1 & 0x10U)); acc |= (uint8_t)((~(uint32_t)(sn1 << 4)) & 0x20U); acc |= (uint8_t)(sn3_inv_shl3 & 0x40U); acc |= 0x80U; perm[2] = acc; const uint8_t sn2_inv_shl6 = (uint8_t) ~(sn2 << 6); - acc = (uint8_t)(((sn0 >> 2) & 2U) | ((sn1 >> 3) & 8U) | hitag2_extract_bits(sn2 ^ 0x20U, 5, 1)); + acc = + (uint8_t)(((sn0 >> 2) & 2U) | ((sn1 >> 3) & 8U) | hitag2_extract_bits(sn2 ^ 0x20U, 5, 1)); acc |= (uint8_t)((sn1 << 5) & 0x20U); acc |= (uint8_t)((sn3 << 1) & 0x40U); acc |= (uint8_t)(((uint8_t)~sn0_shr3) & 4U); @@ -640,9 +641,9 @@ static void hitag2_apply_check_remote( static void renault_v1_check_remote_controller(SubGhzProtocolDecoderRenaultV1* instance) { hitag2_apply_check_remote( instance->generic.data, - instance->generic.data_2, + instance->data_2, instance->recovered, - instance->generic.seed, + instance->seed, &instance->generic.serial, &instance->generic.btn, &instance->generic.cnt, @@ -659,8 +660,7 @@ static bool hitag2_encoder_next_frame( uint32_t orig_hop, uint8_t tail) { uint8_t raw[11]; - if(!hitag2_key_matches_hop( - instance->hitag2_key, orig_uid, orig_btn, orig_cnt10, orig_hop)) { + if(!hitag2_key_matches_hop(instance->hitag2_key, orig_uid, orig_btn, orig_cnt10, orig_hop)) { return false; } @@ -673,7 +673,7 @@ static bool hitag2_encoder_next_frame( uint32_t hop = hitag2_authenticator(uid, tx_btn, cnt10, instance->hitag2_key); hitag2_pack_auth_frame(uid, tx_btn, cnt10, hop, tail, raw); - hitag2_apply_raw(raw, &instance->generic.data, &instance->generic.data_2); + hitag2_apply_raw(raw, &instance->generic.data, &instance->data_2); instance->generic.serial = uid; instance->generic.btn = tx_btn; instance->generic.cnt = cnt10; @@ -771,7 +771,7 @@ static bool renault_v1_encoder_get_upload(SubGhzProtocolEncoderRenaultV1* instan size_t index = 0; LevelDuration* upload = instance->encoder.upload; const uint64_t key = instance->generic.data; - const uint64_t key_2 = instance->generic.data_2 & 0xFFFFFFULL; + const uint64_t key_2 = instance->data_2 & 0xFFFFFFULL; for(size_t i = 0; i < HITAG2_PREAMBLE_PAIRS; i++) { if(!hitag2_encoder_add_level(upload, &index, true, HITAG2_TE_US)) return false; @@ -808,9 +808,7 @@ SubGhzProtocolStatus do { ret = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - renault_v1_const.min_count_bit_for_found); + &instance->generic, flipper_format, renault_v1_const.min_count_bit_for_found); if(ret != SubGhzProtocolStatusOk) { break; } @@ -820,10 +818,9 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserOthers; break; } - instance->generic.data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); + instance->data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); - hitag2_read_recovered_and_seed( - flipper_format, &instance->recovered, &instance->generic.seed); + hitag2_read_recovered_and_seed(flipper_format, &instance->recovered, &instance->seed); if(!flipper_format_rewind(flipper_format)) { ret = SubGhzProtocolStatusErrorParserOthers; @@ -837,7 +834,7 @@ SubGhzProtocolStatus uint16_t wire_cnt10 = 0; hitag2_unpack_frame( instance->generic.data, - instance->generic.data_2, + instance->data_2, &wire_serial, &wire_btn, &wire_cnt10, @@ -845,9 +842,9 @@ SubGhzProtocolStatus &tail); hitag2_apply_check_remote( instance->generic.data, - instance->generic.data_2, + instance->data_2, instance->recovered, - instance->generic.seed, + instance->seed, &instance->generic.serial, &instance->generic.btn, &instance->generic.cnt, @@ -892,14 +889,13 @@ SubGhzProtocolStatus instance->generic.serial, instance->generic.cnt, instance->generic.btn, - instance->generic.seed, + instance->seed, out, iv); instance->generic.data = hitag2_bytes_to_u64_be(out, 8); - instance->generic.data_2 = ((uint64_t)out[8] << 16U) | ((uint64_t)out[9] << 8U) | - out[10]; - instance->generic.seed = ((uint32_t)iv[0] << 24U) | ((uint32_t)iv[1] << 16U) | - ((uint32_t)iv[2] << 8U) | iv[3]; + instance->data_2 = ((uint64_t)out[8] << 16U) | ((uint64_t)out[9] << 8U) | out[10]; + instance->seed = ((uint32_t)iv[0] << 24U) | ((uint32_t)iv[1] << 16U) | + ((uint32_t)iv[2] << 8U) | iv[3]; } else if(!instance->hitag2_key_valid && instance->recovered != HITAG2_RECOVERED_YES) { ret = SubGhzProtocolStatusErrorParserOthers; break; @@ -918,12 +914,14 @@ SubGhzProtocolStatus } uint8_t key_data[8]; hitag2_u64_to_bytes_be(instance->generic.data, key_data, 8); - if(!flipper_format_insert_or_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) { + if(!flipper_format_insert_or_update_hex( + flipper_format, FF_KEY, key_data, sizeof(key_data))) { ret = SubGhzProtocolStatusErrorParserOthers; break; } - hitag2_u64_to_bytes_be(instance->generic.data_2, key_data, 8); - if(!flipper_format_insert_or_update_hex(flipper_format, "Key_2", key_data, sizeof(key_data))) { + hitag2_u64_to_bytes_be(instance->data_2, key_data, 8); + if(!flipper_format_insert_or_update_hex( + flipper_format, "Key_2", key_data, sizeof(key_data))) { ret = SubGhzProtocolStatusErrorParserOthers; break; } @@ -934,7 +932,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserOthers; break; } - if(!hitag2_write_hex_be(flipper_format, "Seed", instance->generic.seed, 4)) { + if(!hitag2_write_hex_be(flipper_format, "Seed", instance->seed, 4)) { ret = SubGhzProtocolStatusErrorParserOthers; break; } @@ -943,7 +941,7 @@ SubGhzProtocolStatus { uint32_t hop = 0; hitag2_unpack_frame( - instance->generic.data, instance->generic.data_2, NULL, NULL, NULL, &hop, NULL); + instance->generic.data, instance->data_2, NULL, NULL, NULL, &hop, NULL); hitag2_write_named_fields( flipper_format, instance->generic.serial, @@ -1028,14 +1026,14 @@ static bool hitag2_accept_frame(SubGhzProtocolDecoderRenaultV1* instance, uint64 return false; } - instance->generic.data_2 = key_2; + instance->data_2 = key_2; instance->generic.data_count_bit = HITAG2_MIN_COUNT_BIT; instance->recovered = 0; - instance->generic.seed = 0; + instance->seed = 0; uint16_t cnt10 = 0; hitag2_unpack_frame( instance->generic.data, - instance->generic.data_2, + instance->data_2, &instance->generic.serial, &instance->generic.btn, &cnt10, @@ -1184,26 +1182,26 @@ SubGhzProtocolStatus subghz_protocol_decoder_renault_v1_serialize( const uint32_t serial = instance->generic.serial; const uint8_t btn = instance->generic.btn; const uint32_t cnt = instance->generic.cnt; - const uint32_t seed = instance->generic.seed; - const uint64_t data_2 = instance->generic.data_2; + const uint32_t seed = instance->seed; + const uint64_t data_2 = instance->data_2; instance->generic.serial = 0; instance->generic.btn = 0; instance->generic.cnt = 0; - instance->generic.seed = 0; - instance->generic.data_2 = 0; + instance->seed = 0; + instance->data_2 = 0; SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset); instance->generic.serial = serial; instance->generic.btn = btn; instance->generic.cnt = cnt; - instance->generic.seed = seed; - instance->generic.data_2 = data_2; + instance->seed = seed; + instance->data_2 = data_2; if(ret != SubGhzProtocolStatusOk) { return ret; } ret = hitag2_write_extra_fields( - flipper_format, instance->generic.data_2, instance->recovered, instance->generic.seed); + flipper_format, instance->data_2, instance->recovered, instance->seed); if(ret != SubGhzProtocolStatusOk) { return ret; } @@ -1225,9 +1223,7 @@ SubGhzProtocolStatus do { ret = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, - flipper_format, - renault_v1_const.min_count_bit_for_found); + &instance->generic, flipper_format, renault_v1_const.min_count_bit_for_found); if(ret != SubGhzProtocolStatusOk) { break; } @@ -1237,10 +1233,9 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserOthers; break; } - instance->generic.data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); + instance->data_2 = hitag2_bytes_to_u64_be(key2, sizeof(key2)); - hitag2_read_recovered_and_seed( - flipper_format, &instance->recovered, &instance->generic.seed); + hitag2_read_recovered_and_seed(flipper_format, &instance->recovered, &instance->seed); renault_v1_check_remote_controller(instance); instance->hitag2_key_valid = false; @@ -1270,7 +1265,7 @@ void subghz_protocol_decoder_renault_v1_get_string(void* context, FuriString* ou "%s\r\nK1:%016llX\r\nK2:%06llX Sn:%08lX\r\nBtn:%02X [%s] %db", instance->generic.protocol_name, (unsigned long long)instance->generic.data, - (unsigned long long)(instance->generic.data_2 & 0xFFFFFFULL), + (unsigned long long)(instance->data_2 & 0xFFFFFFULL), (unsigned long)instance->generic.serial, instance->generic.btn, hitag2_get_button_name(instance->generic.btn), @@ -1280,7 +1275,7 @@ void subghz_protocol_decoder_renault_v1_get_string(void* context, FuriString* ou furi_string_cat_printf( output, "\r\nIV:%08lX Cnt:%04lX", - (unsigned long)instance->generic.seed, + (unsigned long)instance->seed, (unsigned long)(instance->generic.cnt & 0xFFFFU)); } else if(instance->recovered == HITAG2_RECOVERED_BF_MISS) { furi_string_cat_printf(output, "\r\nBF not found"); @@ -1472,21 +1467,19 @@ bool hitag2_bf_state_from_flipper_format(Hitag2BfState* state, FlipperFormat* ff return true; } -bool hitag2_bf_needs_bruteforce(FlipperFormat* ff, bool require_renault_v1) { +bool hitag2_bf_needs_bruteforce(FlipperFormat* ff) { if(!ff) { return false; } FuriString* s = furi_string_alloc(); flipper_format_rewind(ff); - if(require_renault_v1) { - if(!flipper_format_read_string(ff, FF_PROTOCOL, s) || - furi_string_cmp_str(s, RENAULT_PROTOCOL_V1_NAME) != 0) { - furi_string_free(s); - return false; - } - flipper_format_rewind(ff); + if(!flipper_format_read_string(ff, FF_PROTOCOL, s) || + furi_string_cmp_str(s, RENAULT_PROTOCOL_V1_NAME) != 0) { + furi_string_free(s); + return false; } + flipper_format_rewind(ff); bool has_key = flipper_format_read_string(ff, FF_KEY, s); furi_string_free(s); @@ -1515,8 +1508,8 @@ bool hitag2_bf_patch_flipper_format_on_success(FlipperFormat* ff, const Hitag2Bf const uint32_t seed = hitag2_seed_from_iv(state->iv); const uint64_t data = hitag2_bytes_to_u64_be(state->frame, 8); - const uint64_t data_2 = ((uint64_t)state->frame[8] << 16U) | ((uint64_t)state->frame[9] << 8U) | - state->frame[10]; + const uint64_t data_2 = ((uint64_t)state->frame[8] << 16U) | + ((uint64_t)state->frame[9] << 8U) | state->frame[10]; uint32_t serial = 0; uint32_t cnt = 0; uint32_t hop = 0; @@ -1557,8 +1550,8 @@ bool hitag2_flipper_format_get_string(FlipperFormat* ff, FuriString* output) { return false; } - const bool ok = - subghz_protocol_decoder_renault_v1_deserialize(decoder, ff) == SubGhzProtocolStatusOk; + const bool ok = subghz_protocol_decoder_renault_v1_deserialize(decoder, ff) == + SubGhzProtocolStatusOk; if(ok) { furi_string_reset(output); subghz_protocol_decoder_renault_v1_get_string(decoder, output); diff --git a/applications/system/ProtoPirate/protocols/renault_v1.h b/applications/system/ProtoPirate/protocols/renault_v1.h index 4d77e8af..fab01ae9 100644 --- a/applications/system/ProtoPirate/protocols/renault_v1.h +++ b/applications/system/ProtoPirate/protocols/renault_v1.h @@ -37,7 +37,7 @@ struct Hitag2BfState { int32_t hitag2_brute_force_thread_entry(void* arg); bool hitag2_bf_state_from_flipper_format(Hitag2BfState* state, FlipperFormat* ff); -bool hitag2_bf_needs_bruteforce(FlipperFormat* ff, bool require_renault_v1); +bool hitag2_bf_needs_bruteforce(FlipperFormat* ff); bool hitag2_bf_patch_flipper_format_on_success(FlipperFormat* ff, const Hitag2BfState* state); bool hitag2_bf_patch_flipper_format_on_miss(FlipperFormat* ff); bool hitag2_flipper_format_get_string(FlipperFormat* ff, FuriString* output); diff --git a/applications/system/ProtoPirate/protopirate_app.c b/applications/system/ProtoPirate/protopirate_app.c index e5f72911..09cc8f67 100644 --- a/applications/system/ProtoPirate/protopirate_app.c +++ b/applications/system/ProtoPirate/protopirate_app.c @@ -12,6 +12,73 @@ #define TAG "ProtoPirateApp" +// ----------------------------------------------------------------------------- +// Plugin load / unload +// ----------------------------------------------------------------------------- +void config_plugin_unload(ProtoPirateApp* app) { + furi_check(app); + + app->config_plugin = NULL; + + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; + } + + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; + } +} + +bool config_plugin_load(ProtoPirateApp* app) { + furi_check(app); + + if(app->config_plugin) return true; + + if(app->plugin_manager || app->plugin_resolver) { + config_plugin_unload(app); + } + + CompositeApiResolver* resolver = composite_api_resolver_alloc(); + if(!resolver) { + FURI_LOG_E(TAG, "Failed to allocate config plugin resolver"); + return false; + } + composite_api_resolver_add(resolver, firmware_api_interface); + + PluginManager* manager = plugin_manager_alloc( + PROTOPIRATE_CONFIG_PLUGIN_APP_ID, + PROTOPIRATE_CONFIG_PLUGIN_API_VERSION, + composite_api_resolver_get(resolver)); + if(!manager) { + FURI_LOG_E(TAG, "Failed to allocate config plugin manager"); + composite_api_resolver_free(resolver); + return false; + } + + PluginManagerError error = plugin_manager_load_single(manager, CONFIG_PLUGIN_PATH); + if(error != PluginManagerErrorNone) { + FURI_LOG_E(TAG, "Failed to load config plugin %s: %d", CONFIG_PLUGIN_PATH, (int)error); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + + const ProtoPirateConfigPlugin* plugin = plugin_manager_get_ep(manager, 0U); + if(!plugin || !plugin->on_enter) { + FURI_LOG_E(TAG, "Config plugin entry point is invalid"); + plugin_manager_free(manager); + composite_api_resolver_free(resolver); + return false; + } + + app->plugin_resolver = resolver; + app->plugin_manager = manager; + app->config_plugin = plugin; + return true; +} + static bool protopirate_app_custom_event_callback(void* context, uint32_t event) { furi_check(context); ProtoPirateApp* app = context; @@ -88,8 +155,10 @@ ProtoPirateApp* protopirate_app_alloc() { // Apply auto-save setting app->auto_save = settings.auto_save; + app->sound = settings.sound; app->check_saved = settings.check_saved; app->tx_power = settings.tx_power; + app->datetime_filenames = settings.datetime_filenames; #ifdef ENABLE_EMULATE_FEATURE app->emulate_feature_enabled = settings.emulate_feature_enabled; #else @@ -152,7 +221,33 @@ ProtoPirateApp* protopirate_app_alloc() { settings.auto_save, settings.hopping_enabled); - protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); + config_plugin_load(app); + app->car_models_count = app->config_plugin->car_model_get_count(); + app->selected_model = malloc(sizeof(ProtoPirateCarModel)); + app->selected_model->name = furi_string_alloc(); + app->selected_model->preset = NULL; // important initialization + app->selected_model->index = 0; // optional but clean + app->variable_item_list = NULL; + + //Grab selected car model. + if(settings.car_model_index) { + app->config_plugin->car_model_get_by_index( + app->selected_model, settings.car_model_index, app->car_models_count, app->setting); + app->selected_model->last_preset_index = settings.preset_index; + + protopirate_preset_init( + app, + furi_string_get_cstr(app->selected_model->preset->name), + app->selected_model->preset->frequency, + app->selected_model->preset->data, + app->selected_model->preset->data_size); + } else { + app->config_plugin->car_model_get_by_index( + app->selected_model, 0, app->car_models_count, app->setting); + + protopirate_preset_init(app, preset_name, frequency, preset_data, preset_data_size); + } + config_plugin_unload(app); // Apply hopping state from settings app->txrx->hopper_state = settings.hopping_enabled ? ProtoPirateHopperStateRunning : @@ -176,8 +271,10 @@ void protopirate_app_free(ProtoPirateApp* app) { ProtoPirateSettings settings; settings.frequency = app->txrx->preset->frequency; settings.auto_save = app->auto_save; + settings.sound = app->sound; settings.check_saved = app->check_saved; settings.tx_power = app->tx_power; + settings.datetime_filenames = app->datetime_filenames; settings.hopping_enabled = (app->txrx->hopper_state != ProtoPirateHopperStateOFF); #ifdef ENABLE_EMULATE_FEATURE settings.emulate_feature_enabled = app->emulate_feature_enabled; @@ -185,16 +282,48 @@ void protopirate_app_free(ProtoPirateApp* app) { settings.emulate_feature_enabled = false; #endif - // Find current preset index - settings.preset_index = 0; - const char* current_preset = furi_string_get_cstr(app->txrx->preset->name); - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) { - settings.preset_index = i; - break; + //Get the selected Model, and get the preset to save. + if(app->selected_model && app->selected_model->index) { + //Get Preset Index before model was selected. + settings.car_model_index = app->selected_model->index; + settings.preset_index = app->selected_model->last_preset_index; + } else { + // Find current preset index + settings.car_model_index = 0; //Clear the selected model. + settings.preset_index = 0; + const char* current_preset = furi_string_get_cstr(app->txrx->preset->name); + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(strcmp(subghz_setting_get_preset_name(app->setting, i), current_preset) == 0) { + settings.preset_index = i; + break; + } } } + //Free the Model Name + furi_string_free(app->selected_model->name); + app->selected_model->index = 0; + + //Free the preset information. + if(app->selected_model->preset) { + //Free the preset data + if((app->selected_model)->preset->data) { + free(app->selected_model->preset->data); + app->selected_model->preset->data = NULL; + } + + //Free the Preset name + furi_string_free(app->selected_model->preset->name); + + //Free the preset. + free(app->selected_model->preset); + app->selected_model->preset = NULL; + } + + //Free the Model. + free(app->selected_model); + app->selected_model = NULL; + FURI_LOG_I( TAG, "Saving settings: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d", diff --git a/applications/system/ProtoPirate/protopirate_app_i.h b/applications/system/ProtoPirate/protopirate_app_i.h index 08884fc8..6cd0579c 100644 --- a/applications/system/ProtoPirate/protopirate_app_i.h +++ b/applications/system/ProtoPirate/protopirate_app_i.h @@ -3,7 +3,6 @@ #include #include "helpers/protopirate_types.h" -#include "helpers/protopirate_settings.h" #include "scenes/protopirate_scene.h" #include "views/protopirate_receiver.h" #include "protopirate_history.h" @@ -13,7 +12,7 @@ #include #include #include -#include + #include #include #include @@ -33,16 +32,22 @@ #ifdef ENABLE_EMULATE_FEATURE #include "scenes/plugins/protopirate_emulate_plugin.h" #endif +#include "scenes/plugins/protopirate_config_plugin.h" #include "scenes/plugins/protopirate_psa_bf_plugin.h" #include "scenes/plugins/protopirate_tool_scene_plugin.h" #include "helpers/protopirate_views.h" #include "helpers/protopirate_radio.h" #include "helpers/protopirate_protocol_plugin_host.h" #include "helpers/protopirate_txrx.h" +#include "helpers/protopirate_models.h" +#include +#include "helpers/protopirate_settings.h" + +#define CONFIG_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_config_plugin.fal") #define PROTOPIRATE_KEYSTORE_DIR_NAME APP_ASSETS_PATH("encrypted") -typedef struct ProtoPirateApp ProtoPirateApp; +typedef struct VariableItemList VariableItemList; typedef struct ProtoPirateTxRx { SubGhzWorker* worker; @@ -83,6 +88,8 @@ struct ProtoPirateApp { FuriString* loaded_file_path; bool auto_save; bool check_saved; + bool sound; + bool datetime_filenames; bool radio_initialized; uint32_t start_tx_time; uint8_t tx_power; @@ -92,15 +99,16 @@ struct ProtoPirateApp { bool save_from_saved_info; bool emulate_disabled_for_loaded; bool emulate_feature_enabled; + CompositeApiResolver* plugin_resolver; + PluginManager* plugin_manager; #ifdef ENABLE_EMULATE_FEATURE #define EMULATE_NAV_NONE 0U #define EMULATE_NAV_POP 1U #define EMULATE_NAV_STOP_APP 2U - CompositeApiResolver* emulate_plugin_resolver; - PluginManager* emulate_plugin_manager; const ProtoPirateEmulatePlugin* emulate_plugin; uint8_t emulate_nav_pending; #endif + const ProtoPirateConfigPlugin* config_plugin; CompositeApiResolver* psa_bf_plugin_resolver; PluginManager* psa_bf_plugin_manager; const ProtoPiratePsaBfPlugin* psa_bf_plugin; @@ -108,13 +116,15 @@ struct ProtoPirateApp { PluginManager* tool_scene_plugin_manager; const ProtoPirateToolScenePlugin* tool_scene_plugin; ProtoPirateToolScenePluginKind tool_scene_plugin_kind; - #define TOOL_SCENE_NAV_NONE 0U #define TOOL_SCENE_NAV_POP 1U #define TOOL_SCENE_NAV_NEXT 2U #define TOOL_SCENE_NAV_SEARCH_PREVIOUS 3U uint8_t tool_scene_nav_pending; uint32_t tool_scene_nav_target; + + ProtoPirateCarModel* selected_model; + uint32_t car_models_count; }; #ifdef ENABLE_EMULATE_FEATURE @@ -131,6 +141,9 @@ bool protopirate_tool_scene_on_event(void* app, SceneManagerEvent event); void protopirate_tool_scene_on_exit(void* app); void protopirate_tool_scene_plugin_release(ProtoPirateApp* app); +bool config_plugin_load(ProtoPirateApp* app); +void config_plugin_unload(ProtoPirateApp* app); + void protopirate_app_free(ProtoPirateApp* app); static const NotificationSequence sequence_tx = { diff --git a/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.c b/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.c new file mode 100644 index 00000000..f87b07d3 --- /dev/null +++ b/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.c @@ -0,0 +1,494 @@ +#include "protopirate_config_plugin.h" +#include "../../protopirate_app_i.h" +#include "../../helpers/protopirate_models.h" + +#define ON_OFF_COUNT 2 +const char* const on_off_text[ON_OFF_COUNT] = { + "OFF", + "ON", +}; + +const uint32_t hopping_value[ON_OFF_COUNT] = { + ProtoPirateHopperStateOFF, + ProtoPirateHopperStateRunning, +}; + +const char* const sequence_time_text[ON_OFF_COUNT] = { + "Sequential", + "Time", +}; + +#ifdef ENABLE_EMULATE_FEATURE +#define TX_POWER_COUNT 9 +const char* const tx_power_text[TX_POWER_COUNT] = { + "Preset", + "10dBm +", + "7dBm", + "5dBm", + "0dBm", + "-10dBm", + "-15dBm", + "-20dBm", + "-30dBm", +}; +#endif + +bool protopirate_ensure_variable_item_list(ProtoPirateApp* app) { + furi_check(app); + + if(app->variable_item_list) { + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + variable_item_list_free(app->variable_item_list); + //Reassigned below... app->variable_item_list = NULL; + } + + app->variable_item_list = variable_item_list_alloc(); + if(!app->variable_item_list) { + return false; + } + + view_dispatcher_add_view( + app->view_dispatcher, + ProtoPirateViewVariableItemList, + variable_item_list_get_view(app->variable_item_list)); + return true; +} + +static void protopirate_scene_receiver_config_set_frequency(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + if(app->txrx->hopper_state == ProtoPirateHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, index) / 1000000, + (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); + } else { + variable_item_set_current_value_index( + item, subghz_setting_get_frequency_default_index(app->setting)); + } +} + +static void protopirate_scene_receiver_config_set_preset(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, index)); + protopirate_preset_init( + app, + subghz_setting_get_preset_name(app->setting, index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, index), + subghz_setting_get_preset_data_size(app->setting, index)); + + if(!protopirate_refresh_protocol_registry(app, false)) { + notification_message(app->notifications, &sequence_error); + } +} + +static void protopirate_scene_receiver_config_set_hopping_running(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + variable_item_set_current_value_text(item, on_off_text[index]); + if(hopping_value[index] == ProtoPirateHopperStateOFF) { + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_default_frequency(app->setting) / 1000000, + (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + text_buf); + app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + " -----"); + variable_item_set_current_value_index( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + subghz_setting_get_frequency_default_index(app->setting)); + } + + app->txrx->hopper_state = hopping_value[index]; +} + +static void protopirate_scene_receiver_config_set_model(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t direction = variable_item_get_current_value_index(item); + + //Get current selection + uint16_t model_index; + model_index = + (app->selected_model && app->selected_model->index) ? app->selected_model->index : 0; + uint16_t old_model_index = model_index; + + //Cycle the index. + switch(direction) { + case VariableItemListEventCycleReset: { + model_index = 0; + break; + } + case VariableItemListEventCycleLeft: { + if(model_index > 0) + model_index--; + else + model_index = app->car_models_count; + break; + } + case VariableItemListEventCycleRight: { + if(model_index < app->car_models_count) + model_index++; + else + model_index = 0; + } + } + + //Get a Car Model object, and dont forget to shut down on app free! + car_model_get_by_index( + (ProtoPirateCarModel*)app->selected_model, + model_index, + app->car_models_count, + app->setting); + + //Add he car model the list, with the correct selection and text. + variable_item_set_item_label(item, furi_string_get_cstr(app->selected_model->name)); + variable_item_set_current_value_text(item, ""); + variable_item_set_current_value_index(item, 0); + + //Restore Original Preset. + VariableItem* freq_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexFrequency); + VariableItem* hop_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexHopping); + VariableItem* preset_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexModulation); + + //set the Preset, Frequency and Hopper off or restore. + if(model_index) { + //Save Original Preset. + if(!old_model_index) { + if(!strcmp(furi_string_get_cstr(app->txrx->preset->name), "Custom")) { + app->selected_model->last_preset_index = 0; + } else { + app->selected_model->last_preset_index = subghz_setting_get_inx_preset_by_name( + app->setting, furi_string_get_cstr(app->txrx->preset->name)); + } + } + + protopirate_preset_init( + app, + furi_string_get_cstr(app->selected_model->preset->name), + app->selected_model->preset->frequency, + app->selected_model->preset->data, + app->selected_model->preset->data_size); + + variable_item_set_current_value_text(freq_menu, "Locked"); + variable_item_set_current_value_text(hop_menu, "Locked"); + app->txrx->hopper_state = ProtoPirateHopperStateOFF; + //app->txrx->preset->frequency = app->selected_model->preset->frequency; + } else { + //Restore Original Preset. + protopirate_scene_receiver_config_set_frequency(freq_menu); + protopirate_scene_receiver_config_set_hopping_running(hop_menu); + + protopirate_preset_init( + app, + subghz_setting_get_preset_name(app->setting, app->selected_model->last_preset_index), + app->txrx->preset->frequency, + subghz_setting_get_preset_data(app->setting, app->selected_model->last_preset_index), + subghz_setting_get_preset_data_size( + app->setting, app->selected_model->last_preset_index)); + + variable_item_set_current_value_text( + preset_menu, + subghz_setting_get_preset_name(app->setting, app->selected_model->last_preset_index)); + } + + //Refresh the protocol registry now that we have a new Modulation Type. + if(!protopirate_refresh_protocol_registry(app, false)) { + notification_message(app->notifications, &sequence_error); + } + + //Lock or Unlock the menus. + bool lock = (app->selected_model->index != 0); + variable_item_set_locked(freq_menu, lock, "Turn off\nCar Model\nto do that!"); + variable_item_set_locked(hop_menu, lock, "Turn off\nCar Model\nto do that!"); + variable_item_set_locked(preset_menu, lock, "Turn off\nCar Model\nto do that!"); +} + +static uint8_t + protopirate_scene_receiver_config_next_frequency(const uint32_t value, void* context) { + furi_check(context); + ProtoPirateApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { + if(value == subghz_setting_get_frequency(app->setting, i)) { + index = i; + break; + } else { + index = subghz_setting_get_frequency_default_index(app->setting); + } + } + return index; +} + +static uint8_t + protopirate_scene_receiver_config_next_preset(const char* preset_name, void* context) { + furi_check(context); + ProtoPirateApp* app = context; + uint8_t index = 0; + for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { + if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { + index = i; + break; + } + } + return index; +} + +static uint8_t protopirate_scene_receiver_config_hopper_value_index( + const uint32_t value, + const uint32_t values[], + uint8_t values_count, + void* context) { + furi_check(context); + UNUSED(values_count); + ProtoPirateApp* app = context; + + if(value == values[0]) { + return 0; + } else { + variable_item_set_current_value_text( + (VariableItem*)scene_manager_get_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig), + " -----"); + return 1; + } +} + +static void protopirate_scene_receiver_config_set_auto_save(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->auto_save = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +static void protopirate_scene_receiver_config_set_datetime_filenames(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->datetime_filenames = (index == 1); + variable_item_set_current_value_text(item, sequence_time_text[index]); +} + +static void protopirate_scene_receiver_config_set_check_saved(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->check_saved = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +static void protopirate_scene_receiver_config_set_sound(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->sound = (index == 1); + variable_item_set_current_value_text(item, on_off_text[index]); +} + +#ifdef ENABLE_EMULATE_FEATURE +static void protopirate_scene_receiver_config_set_tx_power(VariableItem* item) { + ProtoPirateApp* app = variable_item_get_context(item); + uint8_t index = variable_item_get_current_value_index(item); + + app->tx_power = index; + variable_item_set_current_value_text(item, tx_power_text[index]); +} +#endif + +static void + protopirate_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { + furi_check(context); + ProtoPirateApp* app = context; + + switch(index) { + case ProtoPirateSettingIndexCarModel: { + //Reset the Models Menu + VariableItem* model_menu = + variable_item_list_get(app->variable_item_list, ProtoPirateSettingIndexCarModel); + variable_item_set_current_value_index(model_menu, 0); + protopirate_scene_receiver_config_set_model(model_menu); + break; + } + case ProtoPirateSettingIndexLock: { + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSceneSettingLock); + break; + } + } +} + +static void plugin_on_enter(void* context) { + ProtoPirateApp* app = context; + VariableItem* item; + uint8_t value_index; + + if(!protopirate_ensure_variable_item_list(app)) { + notification_message(app->notifications, &sequence_error); + scene_manager_previous_scene(app->scene_manager); + return; + } + + //Add he car model the list, with the correct selection and text. + item = variable_item_list_add( + app->variable_item_list, + furi_string_get_cstr(app->selected_model->name), + 0, //Plus NONE + protopirate_scene_receiver_config_set_model, + app); + + item = variable_item_list_add( + app->variable_item_list, + "Frequency:", + subghz_setting_get_frequency_count(app->setting), + protopirate_scene_receiver_config_set_frequency, + app); + value_index = + protopirate_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); + scene_manager_set_scene_state( + app->scene_manager, ProtoPirateSceneReceiverConfig, (uint32_t)item); + variable_item_set_current_value_index(item, value_index); + char text_buf[10] = {0}; + snprintf( + text_buf, + sizeof(text_buf), + "%lu.%02lu", + subghz_setting_get_frequency(app->setting, value_index) / 1000000, + (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); + variable_item_set_current_value_text(item, text_buf); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + + item = variable_item_list_add( + app->variable_item_list, + "Hopping:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_hopping_running, + app); + value_index = protopirate_scene_receiver_config_hopper_value_index( + app->txrx->hopper_state, hopping_value, ON_OFF_COUNT, app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, on_off_text[value_index]); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + + item = variable_item_list_add( + app->variable_item_list, + "Modulation:", + subghz_setting_get_preset_count(app->setting), + protopirate_scene_receiver_config_set_preset, + app); + value_index = protopirate_scene_receiver_config_next_preset( + furi_string_get_cstr(app->txrx->preset->name), app); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text( + item, subghz_setting_get_preset_name(app->setting, value_index)); + variable_item_set_locked( + item, + app->selected_model && (app->selected_model->index), + "Turn off\nCar Model\nto do that!"); + +#ifdef ENABLE_EMULATE_FEATURE + // TX power option + item = variable_item_list_add( + app->variable_item_list, + "TX Power:", + TX_POWER_COUNT, + protopirate_scene_receiver_config_set_tx_power, + app); + variable_item_set_current_value_index(item, app->tx_power); + variable_item_set_current_value_text(item, tx_power_text[app->tx_power]); +#endif + // Auto-save option + item = variable_item_list_add( + app->variable_item_list, + "Auto-Save:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_auto_save, + app); + variable_item_set_current_value_index(item, app->auto_save ? 1 : 0); + variable_item_set_current_value_text(item, on_off_text[app->auto_save ? 1 : 0]); + // Date/time filenames option + item = variable_item_list_add( + app->variable_item_list, + "Filenames:", + 2, + protopirate_scene_receiver_config_set_datetime_filenames, + app); + variable_item_set_current_value_index(item, app->datetime_filenames ? 1 : 0); + variable_item_set_current_value_text( + item, sequence_time_text[app->datetime_filenames ? 1 : 0]); + + item = variable_item_list_add( + app->variable_item_list, + "Check Saved:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_check_saved, + app); + variable_item_set_current_value_index(item, app->check_saved ? 1 : 0); + variable_item_set_current_value_text(item, on_off_text[app->check_saved ? 1 : 0]); + + // Sound option + item = variable_item_list_add( + app->variable_item_list, + "Sound:", + ON_OFF_COUNT, + protopirate_scene_receiver_config_set_sound, + app); + variable_item_set_current_value_index(item, app->sound); + variable_item_set_current_value_text(item, on_off_text[app->sound ? 1 : 0]); + + variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); + variable_item_list_set_enter_callback( + app->variable_item_list, protopirate_scene_receiver_config_var_list_enter_callback, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewVariableItemList); +} + +static const ProtoPirateConfigPlugin protopirate_config_plugin = { + .plugin_name = "ProtoPirate Config", + .car_model_get_by_index = car_model_get_by_index, + .car_model_get_count = car_model_get_count, + .on_enter = plugin_on_enter, +}; + +static const FlipperAppPluginDescriptor protopirate_config_plugin_descriptor = { + .appid = PROTOPIRATE_CONFIG_PLUGIN_APP_ID, + .ep_api_version = PROTOPIRATE_CONFIG_PLUGIN_API_VERSION, + .entry_point = &protopirate_config_plugin, +}; + +const FlipperAppPluginDescriptor* protopirate_config_plugin_ep(void) { + return &protopirate_config_plugin_descriptor; +} diff --git a/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.h b/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.h new file mode 100644 index 00000000..95d9462f --- /dev/null +++ b/applications/system/ProtoPirate/scenes/plugins/protopirate_config_plugin.h @@ -0,0 +1,34 @@ +#pragma once + +#include "helpers/protopirate_models.h" +#include +#include "helpers/variable_item_list.h" + +#define PROTOPIRATE_CONFIG_PLUGIN_APP_ID "protopirate_config_plugin" +#define PROTOPIRATE_CONFIG_PLUGIN_API_VERSION 2U + +enum ProtoPirateSettingIndex { + ProtoPirateSettingIndexCarModel, + ProtoPirateSettingIndexFrequency, + ProtoPirateSettingIndexHopping, + ProtoPirateSettingIndexModulation, +#ifdef ENABLE_EMULATE_FEATURE + ProtoPirateSettingIndexTXPower, +#endif + ProtoPirateSettingIndexAutoSave, + ProtoPirateSettingIndexDateTimeFilenames, + ProtoPirateSettingIndexSound, + ProtoPirateSettingIndexCheckSaved, + ProtoPirateSettingIndexLock, +}; + +typedef struct ProtoPirateConfigPlugin { + const char* plugin_name; + bool (*car_model_get_by_index)( + ProtoPirateCarModel* car_model, + uint16_t index, + uint16_t model_count, + SubGhzSetting* app_settings); + uint16_t (*car_model_get_count)(void); + void (*on_enter)(void* app); +} ProtoPirateConfigPlugin; diff --git a/applications/system/ProtoPirate/scenes/plugins/protopirate_emulate_plugin.c b/applications/system/ProtoPirate/scenes/plugins/protopirate_emulate_plugin.c index 76a014da..60aec73c 100644 --- a/applications/system/ProtoPirate/scenes/plugins/protopirate_emulate_plugin.c +++ b/applications/system/ProtoPirate/scenes/plugins/protopirate_emulate_plugin.c @@ -145,8 +145,7 @@ static bool emulate_write_hitag2_key(FlipperFormat* flipper_format, const uint8_ return false; } flipper_format_rewind(flipper_format); - if(!flipper_format_insert_or_update_hex( - flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, 6U)) { + if(!flipper_format_insert_or_update_hex(flipper_format, EMU_PRESET_KEY_HITAG2_KEY, key, 6U)) { return false; } uint32_t epoch = 0U; @@ -313,7 +312,7 @@ static const uint8_t tx_power_value[TX_PRESET_VALUES_COUNT] = { 0x0E, // -20dBm 0x12, // -30dBm - // AM Power Values for 1st PA Table Byte. + // AM Power Values for 2nd PA Table Byte. 0xC0, // 12dBm 0xCD, // 7dBm 0x86, // 5dBm @@ -936,7 +935,15 @@ static void emulate_draw_callback(Canvas* canvas, void* model) { (ctx->freq % 1000000) / 10000); canvas_draw_str(canvas, 2, 30, info_str); - snprintf(info_str, sizeof(info_str), "CNT:%04lX", (unsigned long)ctx->current_counter); + if(ctx->current_counter > 0xFFFF) { + snprintf( + info_str, + sizeof(info_str), + "CNT:...%03lX", + (unsigned long)ctx->current_counter & 0xFFF); + } else { + snprintf(info_str, sizeof(info_str), "CNT:%04lX", (unsigned long)ctx->current_counter); + } canvas_draw_str(canvas, 68, 20, info_str); if(ctx->current_counter > ctx->original_counter) { @@ -1200,14 +1207,13 @@ static void plugin_on_enter(void* context) { furi_string_equal(ctx->protocol_name, "Mitsu v0") || furi_string_equal(ctx->protocol_name, "Mitsubishi V0") || furi_string_equal(ctx->protocol_name, "Mitsubishi v0")) { - uint32_t kia_v0_type = - furi_string_equal(ctx->protocol_name, "Honda V0") ? 3U : - (furi_string_equal(ctx->protocol_name, "Mitsu V0") || - furi_string_equal(ctx->protocol_name, "Mitsu v0") || - furi_string_equal(ctx->protocol_name, "Mitsubishi V0") || - furi_string_equal(ctx->protocol_name, "Mitsubishi v0")) ? - 4U : - 2U; + uint32_t kia_v0_type = furi_string_equal(ctx->protocol_name, "Honda V0") ? 3U : + (furi_string_equal(ctx->protocol_name, "Mitsu V0") || + furi_string_equal(ctx->protocol_name, "Mitsu v0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi V0") || + furi_string_equal(ctx->protocol_name, "Mitsubishi v0")) ? + 4U : + 2U; furi_string_set(ctx->protocol_name, KIA_PROTOCOL_V0_NAME); flipper_format_rewind(ctx->flipper_format); flipper_format_insert_or_update_string_cstr( @@ -1255,8 +1261,8 @@ static void plugin_on_enter(void* context) { uint8_t raw[13] = {0}; bool have_raw = false; flipper_format_rewind(ctx->flipper_format); - if(flipper_format_read_hex(ctx->flipper_format, "Raw", raw, sizeof(raw)) && raw[0] == 0x00U && - raw[1] == 0x01U) { + if(flipper_format_read_hex(ctx->flipper_format, "Raw", raw, sizeof(raw)) && + raw[0] == 0x00U && raw[1] == 0x01U) { have_raw = true; } @@ -1267,8 +1273,10 @@ static void plugin_on_enter(void* context) { } else { uint8_t key_bytes[8] = {0}; flipper_format_rewind(ctx->flipper_format); - if(flipper_format_read_hex(ctx->flipper_format, "Key", key_bytes, sizeof(key_bytes))) { - ctx->serial = ((uint32_t)key_bytes[0] << 24U) | ((uint32_t)key_bytes[1] << 16U) | + if(flipper_format_read_hex( + ctx->flipper_format, "Key", key_bytes, sizeof(key_bytes))) { + ctx->serial = ((uint32_t)key_bytes[0] << 24U) | + ((uint32_t)key_bytes[1] << 16U) | ((uint32_t)key_bytes[2] << 8U) | key_bytes[3]; } } @@ -1279,8 +1287,7 @@ static void plugin_on_enter(void* context) { } } if(have_raw && (ctx->current_counter > 0x3FFU || ctx->original_counter > 0x3FFU)) { - const uint32_t ctrl = - ((uint32_t)(raw[6] & 0x0FU) << 6U) | ((uint32_t)raw[7] >> 2U); + const uint32_t ctrl = ((uint32_t)(raw[6] & 0x0FU) << 6U) | ((uint32_t)raw[7] >> 2U); ctx->original_counter = ctrl; ctx->current_counter = ctrl; flipper_format_rewind(ctx->flipper_format); diff --git a/applications/system/ProtoPirate/scenes/plugins/protopirate_psa_bf_plugin.c b/applications/system/ProtoPirate/scenes/plugins/protopirate_psa_bf_plugin.c index e1586cf1..c0d9f19a 100644 --- a/applications/system/ProtoPirate/scenes/plugins/protopirate_psa_bf_plugin.c +++ b/applications/system/ProtoPirate/scenes/plugins/protopirate_psa_bf_plugin.c @@ -82,17 +82,17 @@ static void bf_free_states(void) { g_bf_kind = ProtoPirateBfKindNone; } -static bool item_needs_bruteforce_from_ff(FlipperFormat* ff, bool require_psa_protocol) { +static bool item_needs_bruteforce_from_ff(FlipperFormat* ff) { if(!ff) return false; FuriString* s = furi_string_alloc(); + flipper_format_rewind(ff); - if(require_psa_protocol) { - if(!flipper_format_read_string(ff, FF_PROTOCOL, s) || furi_string_cmp_str(s, "PSA") != 0) { - furi_string_free(s); - return false; - } - flipper_format_rewind(ff); + if(!flipper_format_read_string(ff, FF_PROTOCOL, s) || furi_string_cmp_str(s, "PSA") != 0) { + furi_string_free(s); + return false; } + + flipper_format_rewind(ff); bool has_key = flipper_format_read_string(ff, FF_KEY, s); if(!has_key) { furi_string_free(s); @@ -121,9 +121,11 @@ static void show_bf_progress(void* app) { if(g_bf_kind == ProtoPirateBfKindHitag2) { widget_add_string_element(widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Recover Key"); widget_add_string_element(widget, 62, 12, AlignLeft, AlignTop, FontSecondary, "Max ETA:"); - widget_add_string_element(widget, 62, 22, AlignLeft, AlignTop, FontSecondary, "60 seconds"); + widget_add_string_element( + widget, 62, 22, AlignLeft, AlignTop, FontSecondary, "60 seconds"); } else { - widget_add_string_element(widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing..."); + widget_add_string_element( + widget, 62, 0, AlignLeft, AlignTop, FontPrimary, "Bruteforcing..."); } FuriString* pct_str = @@ -305,9 +307,9 @@ static void bf_cancel_thread(void) { } static bool plugin_needs_bruteforce(void* app, ProtoPiratePsaBfContext ctx) { + UNUSED(ctx); FlipperFormat* ff = g_host_api->get_history_flipper_format(app); - const bool require = ctx == ProtoPiratePsaBfContextReceiverInfo; - return item_needs_bruteforce_from_ff(ff, require) || hitag2_bf_needs_bruteforce(ff, require); + return item_needs_bruteforce_from_ff(ff) || hitag2_bf_needs_bruteforce(ff); } static bool plugin_is_running(void* app) { @@ -332,8 +334,7 @@ static bool start_bruteforce(void* app) { FlipperFormat* ff = g_host_api->get_history_flipper_format(app); if(!ff || !plugin_needs_bruteforce(app, g_active_ctx)) return false; - const bool require = g_active_ctx == ProtoPiratePsaBfContextReceiverInfo; - if(item_needs_bruteforce_from_ff(ff, require)) { + if(item_needs_bruteforce_from_ff(ff)) { PsaBfState* state = malloc(sizeof(PsaBfState)); if(!state) { g_host_api->notification_error(app); @@ -349,7 +350,7 @@ static bool start_bruteforce(void* app) { g_bf_state = state; g_bf_kind = ProtoPirateBfKindPsa; g_bf_thread = furi_thread_alloc_ex("PsaBf", 2048, psa_brute_force_thread_entry, state); - } else if(hitag2_bf_needs_bruteforce(ff, require)) { + } else if(hitag2_bf_needs_bruteforce(ff)) { Hitag2BfState* state = malloc(sizeof(Hitag2BfState)); if(!state) { g_host_api->notification_error(app); diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_emulate.c b/applications/system/ProtoPirate/scenes/protopirate_scene_emulate.c index f763e362..eb7e7e89 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_emulate.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_emulate.c @@ -94,14 +94,14 @@ static void emulate_plugin_unload(ProtoPirateApp* app) { app->emulate_plugin = NULL; - if(app->emulate_plugin_manager) { - plugin_manager_free(app->emulate_plugin_manager); - app->emulate_plugin_manager = NULL; + if(app->plugin_manager) { + plugin_manager_free(app->plugin_manager); + app->plugin_manager = NULL; } - if(app->emulate_plugin_resolver) { - composite_api_resolver_free(app->emulate_plugin_resolver); - app->emulate_plugin_resolver = NULL; + if(app->plugin_resolver) { + composite_api_resolver_free(app->plugin_resolver); + app->plugin_resolver = NULL; } } @@ -110,7 +110,7 @@ static bool emulate_plugin_load(ProtoPirateApp* app) { if(app->emulate_plugin) return true; - if(app->emulate_plugin_manager || app->emulate_plugin_resolver) { + if(app->plugin_manager || app->plugin_resolver) { emulate_plugin_unload(app); } @@ -148,8 +148,8 @@ static bool emulate_plugin_load(ProtoPirateApp* app) { return false; } - app->emulate_plugin_resolver = resolver; - app->emulate_plugin_manager = manager; + app->plugin_resolver = resolver; + app->plugin_manager = manager; app->emulate_plugin = plugin; plugin->set_host_api(&protopirate_emulate_host_api); diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver.c b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver.c index b9331b1f..14862e4a 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver.c @@ -56,8 +56,10 @@ static void protopirate_scene_receiver_callback( protopirate_history_add_to_history(app->txrx->history, decoder_base, app->txrx->preset); if(added) { - notification_message(app->notifications, &sequence_semi_success); - + if(!(app->sound)) + notification_message(app->notifications, &sequence_semi_success); + else + notification_message(app->notifications, &sequence_single_vibro); FURI_LOG_I( TAG, "Added to history, total items: %u", @@ -107,19 +109,42 @@ static bool protopirate_scene_receiver_process_auto_save(ProtoPirateApp* app) { FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, idx); if(ff) { - FuriString* protocol = furi_string_alloc(); FuriString* saved_path = furi_string_alloc(); + FuriString* file_name_str = furi_string_alloc(); - if(protocol && saved_path) { + if(saved_path && file_name_str) { + if(app->datetime_filenames) { + //Get the date and time to save. + DateTime date_time; + furi_hal_rtc_get_datetime(&date_time); + furi_string_printf( + file_name_str, + "%.2d%.2d%.2d_%.2d.%.2d.%.2d_", + date_time.year, + date_time.month, + date_time.day, + date_time.hour, + date_time.minute, + date_time.second); + } + + // Extract protocol name + FuriString* protocol = furi_string_alloc(); flipper_format_rewind(ff); - if(!flipper_format_read_string(ff, FF_PROTOCOL, protocol)) { + if(!flipper_format_read_string(ff, "Protocol", protocol)) { furi_string_set_str(protocol, "Unknown"); } - furi_string_replace_all(protocol, "/", "_"); - furi_string_replace_all(protocol, " ", "_"); + //Add the protocol + furi_string_cat(file_name_str, protocol); + furi_string_free(protocol); - if(protopirate_storage_save_capture(ff, furi_string_get_cstr(protocol), saved_path)) { + // Clean protocol name for filename + furi_string_replace_all(file_name_str, "/", "_"); + furi_string_replace_all(file_name_str, " ", "_"); + + if(protopirate_storage_save_capture( + ff, furi_string_get_cstr(file_name_str), saved_path, app->datetime_filenames)) { FURI_LOG_I(TAG, "Auto-saved: %s", furi_string_get_cstr(saved_path)); notification_message(app->notifications, &sequence_double_vibro); } else { @@ -131,8 +156,8 @@ static bool protopirate_scene_receiver_process_auto_save(ProtoPirateApp* app) { notification_message(app->notifications, &sequence_error); } - if(protocol) furi_string_free(protocol); if(saved_path) furi_string_free(saved_path); + if(file_name_str) furi_string_free(file_name_str); protopirate_history_release_scratch(app->txrx->history); } else { FURI_LOG_E(TAG, "Auto-save skipped: history capture unavailable"); @@ -226,15 +251,7 @@ static void protopirate_scene_receiver_start_rx_stack(ProtoPirateApp* app) { app->txrx->hopper_state = ProtoPirateHopperStateRunning; } - const char* preset_name = furi_string_get_cstr(app->txrx->preset->name); - uint8_t* preset_data = subghz_setting_get_preset_data_by_name(app->setting, preset_name); - - if(preset_data == NULL) { - FURI_LOG_E(TAG, "Failed to get preset data for %s, using AM650", preset_name); - preset_data = subghz_setting_get_preset_data_by_name(app->setting, "AM650"); - } - - protopirate_begin(app, preset_data); + protopirate_begin(app, app->txrx->preset->data); uint32_t frequency = app->txrx->preset->frequency; if(app->txrx->hopper_state == ProtoPirateHopperStateRunning) { @@ -313,6 +330,9 @@ void protopirate_scene_receiver_on_enter(void* context) { view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewReceiver); view_dispatcher_send_custom_event( app->view_dispatcher, ProtoPirateCustomEventReceiverDeferredRxStart); + + //Kill Config if it exists now to save memory. + protopirate_variable_item_list_free(app); } static void protopirate_scene_receiver_handle_back(ProtoPirateApp* app) { diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_config.c b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_config.c index 2c212721..6bdb3672 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_config.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_config.c @@ -1,302 +1,16 @@ // scenes/protopirate_scene_receiver_config.c #include "../protopirate_app_i.h" -enum ProtoPirateSettingIndex { - ProtoPirateSettingIndexFrequency, - ProtoPirateSettingIndexHopping, - ProtoPirateSettingIndexModulation, -#ifdef ENABLE_EMULATE_FEATURE - ProtoPirateSettingIndexTXPower, -#endif - ProtoPirateSettingIndexAutoSave, - ProtoPirateSettingIndexCheckSaved, - ProtoPirateSettingIndexLock, -}; - -#define HOPPING_COUNT 2 -const char* const hopping_text[HOPPING_COUNT] = { - "OFF", - "ON", -}; - -const uint32_t hopping_value[HOPPING_COUNT] = { - ProtoPirateHopperStateOFF, - ProtoPirateHopperStateRunning, -}; - -#define AUTO_SAVE_COUNT 2 -const char* const auto_save_text[AUTO_SAVE_COUNT] = { - "OFF", - "ON", -}; - -#define CHECK_SAVED_COUNT 2 -const char* const check_saved_text[CHECK_SAVED_COUNT] = { - "OFF", - "ON", -}; - -#ifdef ENABLE_EMULATE_FEATURE -#define TX_POWER_COUNT 9 -const char* const tx_power_text[TX_POWER_COUNT] = { - "Preset", - "10dBm +", - "7dBm", - "5dBm", - "0dBm", - "-10dBm", - "-15dBm", - "-20dBm", - "-30dBm", -}; -#endif - -uint8_t protopirate_scene_receiver_config_next_frequency(const uint32_t value, void* context) { - furi_check(context); - ProtoPirateApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_frequency_count(app->setting); i++) { - if(value == subghz_setting_get_frequency(app->setting, i)) { - index = i; - break; - } else { - index = subghz_setting_get_frequency_default_index(app->setting); - } - } - return index; -} - -uint8_t protopirate_scene_receiver_config_next_preset(const char* preset_name, void* context) { - furi_check(context); - ProtoPirateApp* app = context; - uint8_t index = 0; - for(uint8_t i = 0; i < subghz_setting_get_preset_count(app->setting); i++) { - if(!strcmp(subghz_setting_get_preset_name(app->setting, i), preset_name)) { - index = i; - break; - } - } - return index; -} - -uint8_t protopirate_scene_receiver_config_hopper_value_index( - const uint32_t value, - const uint32_t values[], - uint8_t values_count, - void* context) { - furi_check(context); - UNUSED(values_count); - ProtoPirateApp* app = context; - - if(value == values[0]) { - return 0; - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - " -----"); - return 1; - } -} - -static void protopirate_scene_receiver_config_set_frequency(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - if(app->txrx->hopper_state == ProtoPirateHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, index) / 1000000, - (subghz_setting_get_frequency(app->setting, index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - app->txrx->preset->frequency = subghz_setting_get_frequency(app->setting, index); - } else { - variable_item_set_current_value_index( - item, subghz_setting_get_frequency_default_index(app->setting)); - } -} - -static void protopirate_scene_receiver_config_set_preset(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, index)); - protopirate_preset_init( - app, - subghz_setting_get_preset_name(app->setting, index), - app->txrx->preset->frequency, - subghz_setting_get_preset_data(app->setting, index), - subghz_setting_get_preset_data_size(app->setting, index)); - - if(!protopirate_refresh_protocol_registry(app, false)) { - notification_message(app->notifications, &sequence_error); - } -} - -static void protopirate_scene_receiver_config_set_hopping_running(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - variable_item_set_current_value_text(item, hopping_text[index]); - if(hopping_value[index] == ProtoPirateHopperStateOFF) { - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_default_frequency(app->setting) / 1000000, - (subghz_setting_get_default_frequency(app->setting) % 1000000) / 10000); - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - text_buf); - app->txrx->preset->frequency = subghz_setting_get_default_frequency(app->setting); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } else { - variable_item_set_current_value_text( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - " -----"); - variable_item_set_current_value_index( - (VariableItem*)scene_manager_get_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig), - subghz_setting_get_frequency_default_index(app->setting)); - } - - app->txrx->hopper_state = hopping_value[index]; -} - -static void protopirate_scene_receiver_config_set_auto_save(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->auto_save = (index == 1); - variable_item_set_current_value_text(item, auto_save_text[index]); -} - -static void protopirate_scene_receiver_config_set_check_saved(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->check_saved = (index == 1); - variable_item_set_current_value_text(item, check_saved_text[index]); -} - -#ifdef ENABLE_EMULATE_FEATURE -static void protopirate_scene_receiver_config_set_tx_power(VariableItem* item) { - ProtoPirateApp* app = variable_item_get_context(item); - uint8_t index = variable_item_get_current_value_index(item); - - app->tx_power = index; - variable_item_set_current_value_text(item, tx_power_text[index]); -} -#endif - -static void - protopirate_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) { - furi_check(context); - ProtoPirateApp* app = context; - if(index == ProtoPirateSettingIndexLock) { - view_dispatcher_send_custom_event( - app->view_dispatcher, ProtoPirateCustomEventSceneSettingLock); - } -} - void protopirate_scene_receiver_config_on_enter(void* context) { ProtoPirateApp* app = context; - VariableItem* item; - uint8_t value_index; - if(!protopirate_ensure_variable_item_list(app)) { + if(!config_plugin_load(app)) { notification_message(app->notifications, &sequence_error); scene_manager_previous_scene(app->scene_manager); return; } - item = variable_item_list_add( - app->variable_item_list, - "Frequency:", - subghz_setting_get_frequency_count(app->setting), - protopirate_scene_receiver_config_set_frequency, - app); - value_index = - protopirate_scene_receiver_config_next_frequency(app->txrx->preset->frequency, app); - scene_manager_set_scene_state( - app->scene_manager, ProtoPirateSceneReceiverConfig, (uint32_t)item); - variable_item_set_current_value_index(item, value_index); - char text_buf[10] = {0}; - snprintf( - text_buf, - sizeof(text_buf), - "%lu.%02lu", - subghz_setting_get_frequency(app->setting, value_index) / 1000000, - (subghz_setting_get_frequency(app->setting, value_index) % 1000000) / 10000); - variable_item_set_current_value_text(item, text_buf); - - item = variable_item_list_add( - app->variable_item_list, - "Hopping:", - HOPPING_COUNT, - protopirate_scene_receiver_config_set_hopping_running, - app); - value_index = protopirate_scene_receiver_config_hopper_value_index( - app->txrx->hopper_state, hopping_value, HOPPING_COUNT, app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text(item, hopping_text[value_index]); - - item = variable_item_list_add( - app->variable_item_list, - "Modulation:", - subghz_setting_get_preset_count(app->setting), - protopirate_scene_receiver_config_set_preset, - app); - value_index = protopirate_scene_receiver_config_next_preset( - furi_string_get_cstr(app->txrx->preset->name), app); - variable_item_set_current_value_index(item, value_index); - variable_item_set_current_value_text( - item, subghz_setting_get_preset_name(app->setting, value_index)); - -#ifdef ENABLE_EMULATE_FEATURE - // TX power option - item = variable_item_list_add( - app->variable_item_list, - "TX Power:", - TX_POWER_COUNT, - protopirate_scene_receiver_config_set_tx_power, - app); - variable_item_set_current_value_index(item, app->tx_power); - variable_item_set_current_value_text(item, tx_power_text[app->tx_power]); -#endif - // Auto-save option - item = variable_item_list_add( - app->variable_item_list, - "Auto-Save:", - AUTO_SAVE_COUNT, - protopirate_scene_receiver_config_set_auto_save, - app); - variable_item_set_current_value_index(item, app->auto_save ? 1 : 0); - variable_item_set_current_value_text(item, auto_save_text[app->auto_save ? 1 : 0]); - - item = variable_item_list_add( - app->variable_item_list, - "Check Saved:", - CHECK_SAVED_COUNT, - protopirate_scene_receiver_config_set_check_saved, - app); - variable_item_set_current_value_index(item, app->check_saved ? 1 : 0); - variable_item_set_current_value_text(item, check_saved_text[app->check_saved ? 1 : 0]); - - variable_item_list_add(app->variable_item_list, "Lock Keyboard", 1, NULL, NULL); - variable_item_list_set_enter_callback( - app->variable_item_list, protopirate_scene_receiver_config_var_list_enter_callback, app); - - view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + app->config_plugin->on_enter(app); } bool protopirate_scene_receiver_config_on_event(void* context, SceneManagerEvent event) { @@ -315,6 +29,6 @@ bool protopirate_scene_receiver_config_on_event(void* context, SceneManagerEvent void protopirate_scene_receiver_config_on_exit(void* context) { ProtoPirateApp* app = context; - variable_item_list_set_selected_item(app->variable_item_list, 0); - variable_item_list_reset(app->variable_item_list); + + config_plugin_unload(app); } diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_info.c b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_info.c index 15d29cf6..66ba8fc0 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_info.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_receiver_info.c @@ -280,17 +280,49 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e if(event.event == ProtoPirateCustomEventReceiverInfoSave) { FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->txrx->idx_menu_chosen); - if(ff) { - FuriString* protocol = furi_string_alloc(); - protopirate_storage_get_capture_display_protocol(ff, protocol); + FuriString* filename_str = furi_string_alloc(); + if(ff) { + if(app->datetime_filenames) { + //Get the date and time to save. + DateTime date_time; + furi_hal_rtc_get_datetime(&date_time); + furi_string_printf( + filename_str, + "%.2d%.2d%.2d_%.2d.%.2d.%.2d_", + date_time.year, + date_time.month, + date_time.day, + date_time.hour, + date_time.minute, + date_time.second); + } + + // Extract protocol name + FuriString* protocol = furi_string_alloc(); + flipper_format_rewind(ff); + if(!flipper_format_read_string(ff, "Protocol", protocol)) { + furi_string_set_str(protocol, "Unknown"); + } + + //Add the protocol + furi_string_cat(filename_str, protocol); + //furi_string_free(protocol); + + // Clean protocol name for filename + furi_string_replace_all(filename_str, "/", "_"); + furi_string_replace_all(filename_str, " ", "_"); + + // Get the next auto-generated filename (just the name part) FuriString* auto_path = furi_string_alloc(); if(protopirate_storage_get_next_filename( - furi_string_get_cstr(protocol), auto_path)) { + furi_string_get_cstr(filename_str), auto_path, (app->datetime_filenames))) { + // Extract just the filename without folder and extension const char* full = furi_string_get_cstr(auto_path); const char* slash = strrchr(full, '/'); const char* name_start = slash ? slash + 1 : full; + // Copy without extension size_t name_len = strlen(name_start); const char* dot = strrchr(name_start, '.'); if(dot) name_len = dot - name_start; @@ -304,11 +336,13 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e } furi_string_free(auto_path); + // Store context for when text input confirms if(app->save_protocol) furi_string_free(app->save_protocol); - app->save_protocol = protocol; + app->save_protocol = protocol; // transfer ownership app->save_history_idx = app->txrx->idx_menu_chosen; app->save_from_saved_info = false; + // Configure and show text input text_input_reset(app->text_input); text_input_set_header_text(app->text_input, "Save filename:"); text_input_set_result_callback( @@ -317,17 +351,20 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e app, app->save_filename, sizeof(app->save_filename), - false); + false); // don't clear default text view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewTextInput); } + furi_string_free(filename_str); consumed = true; } if(event.event == ProtoPirateCustomEventReceiverInfoSaveConfirm) { + // User confirmed the filename in text input FlipperFormat* ff = protopirate_history_get_raw_data(app->txrx->history, app->save_history_idx); if(ff) { + // Build full path: folder/filename.psf FuriString* save_path = furi_string_alloc_printf( "%s/%s%s", PROTOPIRATE_APP_FOLDER, @@ -344,11 +381,13 @@ bool protopirate_scene_receiver_info_on_event(void* context, SceneManagerEvent e furi_string_free(save_path); } + // Clean up save protocol string if(app->save_protocol) { furi_string_free(app->save_protocol); app->save_protocol = NULL; } + // Return to the receiver info widget view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); consumed = true; } diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_start.c b/applications/system/ProtoPirate/scenes/protopirate_scene_start.c index 6765bfeb..089724c3 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_start.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_start.c @@ -79,6 +79,9 @@ void protopirate_scene_start_on_enter(void* context) { app->submenu, scene_manager_get_scene_state(app->scene_manager, ProtoPirateSceneStart)); view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewSubmenu); + + //Kill Config if it exists now to save memory. + protopirate_variable_item_list_free(app); } bool protopirate_scene_start_on_event(void* context, SceneManagerEvent event) { @@ -87,6 +90,8 @@ bool protopirate_scene_start_on_event(void* context, SceneManagerEvent event) { bool consumed = false; if(event.type == SceneManagerEventTypeCustom) { + scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneStart, event.event); + FURI_LOG_I(TAG, "Suppressing Charging While in a scene."); if(event.event == SubmenuIndexProtoPirateAbout) { scene_manager_next_scene(app->scene_manager, ProtoPirateSceneAbout); consumed = true; @@ -112,7 +117,6 @@ bool protopirate_scene_start_on_event(void* context, SceneManagerEvent event) { consumed = true; } #endif - scene_manager_set_scene_state(app->scene_manager, ProtoPirateSceneStart, event.event); } return consumed; diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_sub_decode.c b/applications/system/ProtoPirate/scenes/protopirate_scene_sub_decode.c index 6822b6da..90e22dec 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_sub_decode.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_sub_decode.c @@ -493,6 +493,12 @@ static bool protopirate_scene_sub_decode_open_browser_for_next_file(ProtoPirateA return true; } +static void protopirate_scene_sub_decode_text_input_callback(void* context) { + ProtoPirateApp* app = context; + view_dispatcher_send_custom_event( + app->view_dispatcher, ProtoPirateCustomEventSubDecodeSaveConfirm); +} + void protopirate_scene_sub_decode_on_enter(void* context) { ProtoPirateApp* app = context; @@ -621,20 +627,81 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even protopirate_history_get_raw_data(ctx->history, ctx->selected_history_index); if(ff) { + FuriString* file_name_str = furi_string_alloc(); + if(app->datetime_filenames) { + //Get the date and time to save. + DateTime date_time; + furi_hal_rtc_get_datetime(&date_time); + furi_string_printf( + file_name_str, + "%.2d%.2d%.2d_%.2d.%.2d.%.2d_", + date_time.year, + date_time.month, + date_time.day, + date_time.hour, + date_time.minute, + date_time.second); + } + // Extract protocol name FuriString* protocol = furi_string_alloc(); protopirate_storage_get_capture_display_protocol(ff, protocol); - FuriString* saved_path = furi_string_alloc(); - if(protopirate_storage_save_capture( - ff, furi_string_get_cstr(protocol), saved_path)) { - notification_message(app->notifications, &sequence_success); - } else { - notification_message(app->notifications, &sequence_error); - } - + //Add the protocol + furi_string_cat(file_name_str, protocol); furi_string_free(protocol); - furi_string_free(saved_path); + + // Clean protocol name for filename + furi_string_replace_all(file_name_str, "/", "_"); + furi_string_replace_all(file_name_str, " ", "_"); + + // Get the next auto-generated filename (just the name part) + FuriString* auto_path = furi_string_alloc(); + if(protopirate_storage_get_next_filename( + furi_string_get_cstr(file_name_str), auto_path, app->datetime_filenames)) { + // Extract just the filename without folder and extension + const char* full = furi_string_get_cstr(auto_path); + const char* slash = strrchr(full, '/'); + const char* name_start = slash ? slash + 1 : full; + + // Copy without extension + size_t name_len = strlen(name_start); + const char* dot = strrchr(name_start, '.'); + if(dot) name_len = dot - name_start; + if(name_len >= sizeof(app->save_filename)) + name_len = sizeof(app->save_filename) - 1; + + memcpy(app->save_filename, name_start, name_len); + app->save_filename[name_len] = '\0'; + } else { + snprintf(app->save_filename, sizeof(app->save_filename), "capture"); + } + furi_string_free(auto_path); + + // Store context for when text input confirms + app->save_history_idx = app->txrx->idx_menu_chosen; + app->save_from_saved_info = false; + + //Make sure we have a text input window. + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, + ProtoPirateViewTextInput, + text_input_get_view(app->text_input)); + + // Configure and show text input + text_input_reset(app->text_input); + text_input_set_header_text(app->text_input, "Save filename:"); + text_input_set_result_callback( + app->text_input, + protopirate_scene_sub_decode_text_input_callback, + app, + app->save_filename, + sizeof(app->save_filename), + false); // don't clear default text + + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewTextInput); + furi_string_free(file_name_str); } else { FURI_LOG_E( TAG, @@ -643,6 +710,40 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even notification_message(app->notifications, &sequence_error); } consumed = true; + } else if(event.event == ProtoPirateCustomEventSubDecodeSaveConfirm) { + // User confirmed the filename in text input + FlipperFormat* ff = + protopirate_history_get_raw_data(app->txrx->history, app->save_history_idx); + if(ff) { + // Build full path: folder/filename.psf + FuriString* save_path = furi_string_alloc_printf( + "%s/%s%s", + PROTOPIRATE_APP_FOLDER, + app->save_filename, + PROTOPIRATE_APP_EXTENSION); + + if(protopirate_storage_save_capture_to_path(ff, furi_string_get_cstr(save_path))) { + notification_message(app->notifications, &sequence_success); + FURI_LOG_I(TAG, "Saved to: %s", furi_string_get_cstr(save_path)); + } else { + notification_message(app->notifications, &sequence_error); + FURI_LOG_E(TAG, "Save failed"); + } + furi_string_free(save_path); + } + + // Return to the receiver info widget + view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewWidget); + + //Kill the text_input view. + if(app->text_input) { + FURI_LOG_D(TAG, "Removing text_input view"); + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewTextInput); + text_input_free(app->text_input); + app->text_input = NULL; + } + consumed = true; + } #ifdef ENABLE_EMULATE_FEATURE else if( @@ -678,7 +779,6 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even } consumed = true; return consumed; - } else if(event.event == ProtoPirateCustomEventPsaBruteforceComplete) { app->txrx->idx_menu_chosen = ctx->selected_history_index; if(app->psa_bf_plugin) { @@ -689,7 +789,6 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even app->view_dispatcher, ProtoPirateCustomEventSubDecodeUpdate); consumed = true; return consumed; - } else if(event.event == ProtoPirateCustomEventViewReceiverOK) { // User selected a signal from history - show signal info uint16_t idx = protopirate_view_receiver_get_idx_menu(app->protopirate_receiver); @@ -1213,7 +1312,7 @@ bool protopirate_scene_sub_decode_on_event(void* context, SceneManagerEvent even flipper_format_rewind(ff); bool have_proto = flipper_format_read_string(ff, FF_PROTOCOL, proto_str); bool offers_bf = have_proto && protopirate_protocol_catalog_offers_bruteforce( - furi_string_get_cstr(proto_str)); + furi_string_get_cstr(proto_str)); if(have_proto) { const char* protocol_name = furi_string_get_cstr(proto_str); diff --git a/applications/system/ProtoPirate/scenes/protopirate_scene_timing_tuner.c b/applications/system/ProtoPirate/scenes/protopirate_scene_timing_tuner.c index 9848e0cf..8da432d2 100644 --- a/applications/system/ProtoPirate/scenes/protopirate_scene_timing_tuner.c +++ b/applications/system/ProtoPirate/scenes/protopirate_scene_timing_tuner.c @@ -727,6 +727,13 @@ void protopirate_scene_timing_tuner_on_enter(void* context) { g_timing_ctx->is_receiving = true; view_dispatcher_switch_to_view(app->view_dispatcher, ProtoPirateViewAbout); + + //Kill Config if it exists now to save memory. + if(app->variable_item_list) { + view_dispatcher_remove_view(app->view_dispatcher, ProtoPirateViewVariableItemList); + variable_item_list_free(app->variable_item_list); + app->variable_item_list = NULL; + } } bool protopirate_scene_timing_tuner_on_event(void* context, SceneManagerEvent event) { diff --git a/applications/system/ProtoPirate/views/protopirate_receiver.c b/applications/system/ProtoPirate/views/protopirate_receiver.c index a71d4597..2dcc99ca 100644 --- a/applications/system/ProtoPirate/views/protopirate_receiver.c +++ b/applications/system/ProtoPirate/views/protopirate_receiver.c @@ -259,40 +259,6 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo protopirate_view_receiver_draw_progress_badge(canvas, model->sub_decode_progress); } - //Draw To Unlock, Locked etc... - if(model->lock_count) { - if(model->sub_decode_mode) { - canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); - } else { - canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); - } - canvas_set_font(canvas, FontSecondary); - elements_bold_rounded_frame(canvas, 14, 8, 99, 48); - elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); - canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); - canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); - canvas_draw_dot(canvas, 17, 61); - } else { - if(model->lock == ProtoPirateLockOn) { - canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); - canvas_draw_str(canvas, 74, 62, "Locked"); - } else { - if(model->sub_decode_mode) { - canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); - } else { - canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); - canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str)); - canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); - } - } - } - //Draw the List, or the Radar/Dolphin View. if(item_count > 0) { // Draw received items list @@ -411,6 +377,40 @@ void protopirate_view_receiver_draw(Canvas* canvas, ProtoPirateReceiverModel* mo canvas, 110 - canvas_string_width(canvas, auto_save_text), 7, auto_save_text); } } + + //Draw the unlock instructions last, so they appear on top... + if(model->lock_count) { + if(model->sub_decode_mode) { + canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); + } else { + canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); + } + canvas_set_font(canvas, FontSecondary); + elements_bold_rounded_frame(canvas, 14, 8, 99, 48); + elements_multiline_text(canvas, 65, 26, "To unlock\npress:"); + canvas_draw_icon(canvas, 65, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 80, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 95, 42, &I_Pin_back_arrow_10x8); + canvas_draw_icon(canvas, 16, 13, &I_WarningDolphin_45x42); + canvas_draw_dot(canvas, 17, 61); + } else { + if(model->lock == ProtoPirateLockOn) { + canvas_draw_icon(canvas, 64, 55, &I_Lock_7x8); + canvas_draw_str(canvas, 74, 62, "Locked"); + } else { + if(model->sub_decode_mode) { + canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); + } else { + canvas_draw_str(canvas, 44, 63, furi_string_get_cstr(model->frequency_str)); + canvas_draw_str(canvas, 79, 63, furi_string_get_cstr(model->preset_str)); + canvas_draw_str(canvas, 96, 63, furi_string_get_cstr(model->history_stat_str)); + } + } + } } bool protopirate_view_receiver_input(InputEvent* event, void* context) { diff --git a/lib/subghz/protocols/psa.c b/lib/subghz/protocols/psa.c index 57fffb20..835b5252 100644 --- a/lib/subghz/protocols/psa.c +++ b/lib/subghz/protocols/psa.c @@ -19,6 +19,7 @@ static const SubGhzBlockConst subghz_protocol_psa_const = { #define PSA_TE_SHORT_125 0x7d #define PSA_TE_LONG_250 0xfa +#define PSA_TE_LONG_300 0x12c #define PSA_TE_END_1000 1000 #define PSA_TE_END_500 500 #define PSA_TOLERANCE_99 99 @@ -676,7 +677,10 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati } else { tolerance = duration - PSA_TE_SHORT_125; } - if(tolerance > PSA_TOLERANCE_49) { + if(tolerance > 40) { + return; + } + if(duration > 180) { return; } new_state = PSADecoderState3; @@ -960,43 +964,43 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati break; case PSADecoderState3: - if(level) { - return; + // Fixed-TE preamble path (te_short=125us, te_long=250us), aligned with + // ProtoPirate reference decoder. + if(duration >= 250) { + if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) { + if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) { + new_state = PSADecoderState4; + instance->decode_data_low = 0; + instance->decode_data_high = 0; + instance->decode_count_bit = 0; + manchester_advance(instance->manchester_state, ManchesterEventReset, + &instance->manchester_state, NULL); + instance->state = new_state; + instance->pattern_counter = 0; + instance->prev_duration = duration; + return; + } + } + new_state = PSADecoderState0; + instance->pattern_counter = 0; + break; } - // Adaptive AM preamble: accept 76-174us, average to detect actual TE - if(duration >= 76 && duration <= 174) { - if(prev_dur >= 76 && prev_dur <= 174) { + if(duration < PSA_TE_SHORT_125) { + tolerance = PSA_TE_SHORT_125 - duration; + } else { + tolerance = duration - PSA_TE_SHORT_125; + } + + if(tolerance < PSA_TOLERANCE_50) { + uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125); + if(prev_diff <= PSA_TOLERANCE_49) { instance->pattern_counter++; - instance->te_sum += duration; - instance->te_count++; } else { instance->pattern_counter = 0; - instance->te_sum = duration; - instance->te_count = 1; } instance->prev_duration = duration; return; - } else { - // Check if this is the preamble-to-data transition (2x detected TE) - uint32_t te_avg = (instance->te_count > 0) ? - (instance->te_sum / instance->te_count) : PSA_TE_SHORT_125; - uint32_t te_long_expected = te_avg * 2; - uint32_t long_diff = psa_abs_diff(duration, te_long_expected); - - if(long_diff <= te_avg && instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) { - instance->te_detected = te_avg; - new_state = PSADecoderState4; - instance->decode_data_low = 0; - instance->decode_data_high = 0; - instance->decode_count_bit = 0; - manchester_advance(instance->manchester_state, ManchesterEventReset, - &instance->manchester_state, NULL); - instance->state = new_state; - instance->pattern_counter = 0; - instance->prev_duration = duration; - return; - } } new_state = PSADecoderState0; @@ -1009,16 +1013,63 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati break; } - uint32_t te_s = instance->te_detected ? instance->te_detected : PSA_TE_SHORT_125; - uint32_t te_l = te_s * 2; - uint32_t te_tol = te_s / 2; - uint32_t midpoint = (te_s + te_l) / 2; + // Fixed-TE Manchester decode (te_short=125us, te_long=250us), aligned with + // ProtoPirate reference decoder. Bits are consumed on the falling edge (!level); + // the end marker is a HIGH pulse (level) near PSA_TE_END_500. + if(!level) { + uint8_t manchester_input; + bool decoded_bit = false; + + if(duration < PSA_TE_SHORT_125) { + tolerance = PSA_TE_SHORT_125 - duration; + if(tolerance > PSA_TOLERANCE_49) { + return; + } + manchester_input = ((level ^ 1) & 0x7f) << 1; + } else { + tolerance = duration - PSA_TE_SHORT_125; + if(tolerance < PSA_TOLERANCE_50) { + manchester_input = ((level ^ 1) & 0x7f) << 1; + } else if(duration >= PSA_TE_LONG_250 && duration < PSA_TE_LONG_300) { + if(level == 0) { + manchester_input = 6; + } else { + manchester_input = 4; + } + } else { + return; + } + } + + if(instance->decode_count_bit < PSA_KEY2_BITS && + manchester_advance(instance->manchester_state, + (ManchesterEvent)manchester_input, + &instance->manchester_state, + &decoded_bit)) { + uint32_t carry = (instance->decode_data_low >> 31) & 1; + instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0); + instance->decode_data_high = (instance->decode_data_high << 1) | carry; + instance->decode_count_bit++; + + if(instance->decode_count_bit == PSA_KEY1_BITS) { + instance->key1_low = instance->decode_data_low; + instance->key1_high = instance->decode_data_high; + instance->decode_data_low = 0; + instance->decode_data_high = 0; + } + } + } else if(level) { + uint32_t end_diff; + if(duration < PSA_TE_END_500) { + end_diff = PSA_TE_END_500 - duration; + } else { + end_diff = duration - PSA_TE_END_500; + } + if(end_diff <= 99) { + if(instance->decode_count_bit != PSA_KEY2_BITS) { + return; + } - // End marker check: HIGH pulse beyond long range at 80 bits - if(level && instance->decode_count_bit == PSA_KEY2_BITS && duration > midpoint) { - uint32_t end_expected = te_s * 4; - uint32_t end_diff = psa_abs_diff(duration, end_expected); - if(end_diff <= te_s * 2) { instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF); instance->key2_low = instance->decode_data_low; instance->key2_high = instance->decode_data_high; @@ -1058,52 +1109,9 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati new_state = PSADecoderState0; instance->state = new_state; return; - } - } - - // Manchester decode: process BOTH high and low pulses (unlike original AM path) - if(duration > te_l + te_tol) { - if(duration > 10000) { - new_state = PSADecoderState0; - break; - } - return; - } - - uint8_t manchester_input; - bool decoded_bit = false; - - if(duration <= midpoint) { - if(psa_abs_diff(duration, te_s) > te_tol) { + } else { return; } - manchester_input = level ? ManchesterEventShortLow : ManchesterEventShortHigh; - } else { - if(psa_abs_diff(duration, te_l) > te_tol) { - return; - } - manchester_input = level ? ManchesterEventLongLow : ManchesterEventLongHigh; - } - - if(instance->decode_count_bit < PSA_KEY2_BITS) { - if(manchester_advance(instance->manchester_state, - (ManchesterEvent)manchester_input, - &instance->manchester_state, - &decoded_bit)) { - uint32_t carry = (instance->decode_data_low >> 31) & 1; - // PSA AM uses inverted Manchester convention - decoded_bit = !decoded_bit; - instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0); - instance->decode_data_high = (instance->decode_data_high << 1) | carry; - instance->decode_count_bit++; - - if(instance->decode_count_bit == PSA_KEY1_BITS) { - instance->key1_low = instance->decode_data_low; - instance->key1_high = instance->decode_data_high; - instance->decode_data_low = 0; - instance->decode_data_high = 0; - } - } } break; } diff --git a/lib/subghz/protocols/toyota.c b/lib/subghz/protocols/toyota.c index c6d85538..76909bce 100644 --- a/lib/subghz/protocols/toyota.c +++ b/lib/subghz/protocols/toyota.c @@ -128,6 +128,36 @@ static const SubGhzBlockConst toyota_const_b = { #define TOYOTA_A_KIA_SYNC_MIN 901u #define TOYOTA_A_KIA_SYNC_MAX 1600u +/* ---------------------------------------------------------------- + * Physical constants — Variant C (Prius 2006 / Corolla Verso, 433.92 MHz) + * + * KeeLoq-style, DECODE ONLY. Framing: + * >=6 short preamble pairs (HIGH ~200-550us, LOW ~200-650us), then ONE + * extra-long LOW sync gap (1050-1500us), then PWM data. + * DATA bit is decided by the HIGH pulse: + * HIGH > C_HIGH_MIDPOINT (600us) => bit 1, else bit 0 + * (HIGH must be within 200-1050us to be valid). The following LOW is + * complementary and ignored unless it's a sync gap (1050-1500 => fire+restart + * data) or oversized (>1000 ends frame -> hunt preamble). + * Accumulate up to C_FRAME_BITS bits; DECODE when bit_count >= C_BITS. + * Fields MSB-first from the START of the data (anchored to sync gap): + * hop = bits[0..32), serial = bits[32..60), button = bits[60..64). + * ---------------------------------------------------------------- */ + +#define TOYOTA_C_BITS 66u /* MIN data bits required to decode */ +#define TOYOTA_C_FRAME_BITS 68u /* nominal data frame length */ +#define TOYOTA_C_PREAMBLE_MIN 6u /* require >= this many preamble pairs */ +#define TOYOTA_C_SYNC_MIN 1050u /* XL LOW between preamble and data */ +#define TOYOTA_C_SYNC_MAX 1500u +#define TOYOTA_C_HIGH_MIDPOINT 600u /* HIGH >mid => bit 1, else bit 0 */ +#define TOYOTA_C_PRE_HIGH_MIN 200u /* preamble HIGH window */ +#define TOYOTA_C_PRE_HIGH_MAX 550u +#define TOYOTA_C_PRE_LOW_MIN 200u /* preamble LOW window (below sync) */ +#define TOYOTA_C_PRE_LOW_MAX 650u +#define TOYOTA_C_DATA_HIGH_MIN 200u /* valid data HIGH window */ +#define TOYOTA_C_DATA_HIGH_MAX 1050u +#define TOYOTA_C_END_LOW 1000u /* LOW longer than this ends the frame */ + /* ---------------------------------------------------------------- * Button codes * ---------------------------------------------------------------- */ @@ -138,6 +168,11 @@ static const SubGhzBlockConst toyota_const_b = { #define TOYOTA_B_BTN_LOCK 0x0A #define TOYOTA_B_BTN_UNLOCK 0x05 +#define TOYOTA_C_BTN_LOCK 0x0B +#define TOYOTA_C_BTN_UNLOCK 0x0E +#define TOYOTA_C_BTN_TRUNK 0x0D +#define TOYOTA_C_BTN_PANIC 0x07 + /* ---------------------------------------------------------------- * Parser states * ---------------------------------------------------------------- */ @@ -148,6 +183,8 @@ typedef enum { ToyotaStepDataA, ToyotaStepPreambleB, ToyotaStepDataB, + ToyotaStepPreambleC, + ToyotaStepDataC, } ToyotaDecoderStep; /* ---------------------------------------------------------------- @@ -167,7 +204,7 @@ typedef struct { bool have_high; uint16_t preamble_count; - uint8_t variant; /* 0 = Corolla/433MHz, 1 = Tundra/315MHz */ + uint8_t variant; /* 0 = Corolla/433MHz, 1 = Tundra/315MHz, 2 = Prius/433MHz */ uint32_t hop; uint32_t serial; @@ -248,6 +285,15 @@ static uint32_t toyota_extract( * ---------------------------------------------------------------- */ static const char* toyota_button_name(uint8_t btn, uint8_t variant) { + if(variant == 2) { + switch(btn & 0x0F) { + case TOYOTA_C_BTN_LOCK: return "Lock"; + case TOYOTA_C_BTN_UNLOCK: return "Unlock"; + case TOYOTA_C_BTN_TRUNK: return "Trunk"; + case TOYOTA_C_BTN_PANIC: return "Panic"; + default: return "Unknown"; + } + } if(variant == 1) { switch(btn & 0x0F) { case TOYOTA_B_BTN_LOCK: return "Lock"; @@ -268,6 +314,7 @@ static const char* toyota_button_name(uint8_t btn, uint8_t variant) { } static const char* toyota_model_name(uint8_t variant) { + if(variant == 2) return "Prius"; return (variant == 1) ? "Tundra" : "Corolla"; } @@ -283,7 +330,11 @@ static bool toyota_frame_plausible(uint32_t serial, uint8_t button, uint32_t hop // Button must belong to the closed set of Toyota-observed codes. const uint8_t b = button & 0x0FU; - if(variant == 1U) { + if(variant == 2U) { + // Prius variant C: Lock=0x0B, Unlock=0x0E, Trunk=0x0D, Panic=0x07 + if(b != TOYOTA_C_BTN_LOCK && b != TOYOTA_C_BTN_UNLOCK && + b != TOYOTA_C_BTN_TRUNK && b != TOYOTA_C_BTN_PANIC) return false; + } else if(variant == 1U) { // Tundra variant B: Lock=0x0A, Unlock=0x05, L+U=0x0F, Trunk=0x04 if(b != TOYOTA_B_BTN_LOCK && b != TOYOTA_B_BTN_UNLOCK && b != 0x0FU && b != 0x04U) return false; @@ -601,6 +652,162 @@ static void toyota_feed_variant_b( } } +/* ---------------------------------------------------------------- + * FEED — Variant C (Prius 2006 / Corolla Verso, 433 MHz) + * + * Framing: >=6 short preamble pairs, then ONE extra-long LOW sync gap + * (1050-1500us), then PWM data where the HIGH pulse decides the bit + * (HIGH > 600us => 1, else 0). Bits are pushed MSB-first from the START of + * the data (anchored to the sync gap), so a jittery burst delivering 66..69 + * HIGH pulses still decodes. Field layout over the leading bits (MSB-first): + * [32 hop][28 serial][8 tail]; button = high nibble of tail (bits[60..64)). + * + * NOTE on extraction: the JS _extractMSB(start,length) uses + * pos = (total-1) - (start+i) + * which is IDENTICAL to the existing C toyota_extract(inst, offset, length) + * with offset=start. So we reuse toyota_extract directly: + * hop = toyota_extract(inst, 0, 32) + * serial = toyota_extract(inst, 32, 28) + * button = toyota_extract(inst, 60, 4) & 0x0F + * ---------------------------------------------------------------- */ + +static void toyota_start_data_c(SubGhzProtocolDecoderToyota* inst) { + inst->bits_lo = 0; + inst->bits_hi = 0; + inst->bit_count = 0; + inst->have_high = false; + inst->decoder.parser_step = ToyotaStepDataC; +} + +static void toyota_decode_and_fire_c(SubGhzProtocolDecoderToyota* inst) { + if(inst->bit_count < TOYOTA_C_BITS) return; + + inst->hop = toyota_extract(inst, 0, 32); + inst->serial = toyota_extract(inst, 32, 28); + inst->button = (uint8_t)(toyota_extract(inst, 60, 4) & 0x0F); + + if(!toyota_frame_plausible(inst->serial, inst->button, inst->hop, 2)) { + FURI_LOG_D(TAG, "REJECT(C): implausible serial=%08lX btn=%X hop=%08lX", + (unsigned long)inst->serial, (unsigned int)inst->button, + (unsigned long)inst->hop); + return; + } + + inst->variant = 2; + + inst->generic.data = + ((uint64_t)inst->hop << 32) | + ((uint64_t)inst->serial << 4) | + ((uint64_t)inst->button & 0x0F); + + inst->generic.data_count_bit = inst->bit_count; + inst->generic.serial = inst->serial; + inst->generic.btn = inst->button; + inst->generic.cnt = inst->variant; + + inst->decoder.decode_data = inst->generic.data; + inst->decoder.decode_count_bit = inst->generic.data_count_bit; + + FURI_LOG_D(TAG, "FIRE(C) bits=%d hop=%08lX serial=%07lX btn=%X", + (int)inst->bit_count, + (unsigned long)inst->hop, + (unsigned long)inst->serial, + (unsigned int)inst->button); + + if(inst->base.callback) + inst->base.callback(&inst->base, inst->base.context); +} + +static void toyota_feed_variant_c( + SubGhzProtocolDecoderToyota* inst, + bool level, uint32_t duration) +{ + /* ── PREAMBLE ── */ + if(inst->decoder.parser_step == ToyotaStepPreambleC) { + + if(level) { + /* Remember the HIGH; validate it as a preamble HIGH on the LOW. */ + inst->te_last = duration; + inst->have_high = true; + return; + } + + if(!inst->have_high) { + subghz_protocol_decoder_toyota_reset(inst); + return; + } + inst->have_high = false; + + /* XL LOW = sync gap: with enough preamble, begin data. */ + if(duration >= TOYOTA_C_SYNC_MIN && duration <= TOYOTA_C_SYNC_MAX) { + if(inst->preamble_count >= TOYOTA_C_PREAMBLE_MIN) { + toyota_start_data_c(inst); + } else { + subghz_protocol_decoder_toyota_reset(inst); + } + return; + } + + /* Count a preamble pair if HIGH & LOW both fit the (wide) preamble + * windows. Otherwise the sequence is noise -> restart. */ + if(inst->te_last >= TOYOTA_C_PRE_HIGH_MIN && inst->te_last <= TOYOTA_C_PRE_HIGH_MAX && + duration >= TOYOTA_C_PRE_LOW_MIN && duration <= TOYOTA_C_PRE_LOW_MAX) { + inst->preamble_count++; + return; + } + + subghz_protocol_decoder_toyota_reset(inst); + return; + } + + /* ── DATA (PWM, HIGH decides bit) ── */ + if(inst->decoder.parser_step == ToyotaStepDataC) { + + if(level) { + /* HIGH pulse decides the bit (midpoint classifier). Reject HIGHs + * that are implausibly short/long -> end frame (maybe noise). */ + if(duration >= TOYOTA_C_DATA_HIGH_MIN && duration <= TOYOTA_C_DATA_HIGH_MAX) { + if(inst->bit_count < TOYOTA_C_FRAME_BITS) { + toyota_push_bit(inst, (duration > TOYOTA_C_HIGH_MIDPOINT) ? 1 : 0); + } + inst->have_high = true; + inst->te_last = duration; + } else { + /* Out-of-range HIGH: terminate current frame. */ + if(inst->bit_count >= TOYOTA_C_BITS) toyota_decode_and_fire_c(inst); + /* A large HIGH could be a preamble HIGH of the next burst; + * restart preamble tracking with this HIGH. */ + subghz_protocol_decoder_toyota_reset(inst); + inst->variant = 2; + inst->decoder.parser_step = ToyotaStepPreambleC; + inst->te_last = duration; + inst->have_high = true; + } + return; + } + + /* LOW pulse (complementary / gap). */ + inst->have_high = false; + + if(duration >= TOYOTA_C_SYNC_MIN && duration <= TOYOTA_C_SYNC_MAX) { + /* A fresh sync gap: fire the frame we have, then start a new one. */ + if(inst->bit_count >= TOYOTA_C_BITS) toyota_decode_and_fire_c(inst); + toyota_start_data_c(inst); + return; + } + + if(duration > TOYOTA_C_END_LOW) { + /* Oversized gap ends the frame; go back to hunting a preamble. */ + if(inst->bit_count >= TOYOTA_C_BITS) toyota_decode_and_fire_c(inst); + subghz_protocol_decoder_toyota_reset(inst); + inst->variant = 2; + inst->decoder.parser_step = ToyotaStepPreambleC; + inst->preamble_count = 0; + } + /* else: normal short/medium complementary LOW -> ignore. */ + } +} + /* ---------------------------------------------------------------- * Public feed — dispatcher * ---------------------------------------------------------------- */ @@ -631,12 +838,21 @@ void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t dur FURI_LOG_D(TAG, "Detected Variant B (Tundra), first HIGH=%lu", (unsigned long)duration); } else if(fits_a) { - inst->variant = 0; + /* + * Variant A and C share 400/800 timing. Real-world 433 fobs + * (Prius / Corolla Verso) use the Variant C framing (short-short + * preamble + XL sync gap + PWM data where the HIGH decides the + * bit), so route the shared-timing path to the Variant C state + * machine. The Variant A state machine remains reachable via the + * dispatcher's variant==0 fallback (kept referenced to avoid + * -Werror=unused-function). + */ + inst->variant = 2; inst->te_last = duration; inst->have_high = true; inst->preamble_count = 0; - inst->decoder.parser_step = ToyotaStepPreambleA; - FURI_LOG_D(TAG, "Detected Variant A (Corolla), first HIGH=%lu", + inst->decoder.parser_step = ToyotaStepPreambleC; + FURI_LOG_D(TAG, "Detected Variant C (Prius/Corolla Verso), first HIGH=%lu", (unsigned long)duration); } return; @@ -644,6 +860,8 @@ void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t dur if(inst->variant == 1) { toyota_feed_variant_b(inst, level, duration); + } else if(inst->variant == 2) { + toyota_feed_variant_c(inst, level, duration); } else { toyota_feed_variant_a(inst, level, duration); } @@ -687,11 +905,20 @@ SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize( furi_assert(context); SubGhzProtocolDecoderToyota* inst = context; + /* + * Accept any saved frame whose bit count is at least TOYOTA_C_BITS (66). + * Variant B is exactly 67, Variant A exactly 68, and Variant C real + * captures deliver 66-69 bits, so an exact-match check cannot cover all + * three. Use the plain deserializer plus a lower-bound guard. + */ SubGhzProtocolStatus ret = - subghz_block_generic_deserialize_check_count_bit( - &inst->generic, - flipper_format, - toyota_const_b.min_count_bit_for_found); + subghz_block_generic_deserialize(&inst->generic, flipper_format); + + if(ret == SubGhzProtocolStatusOk && + inst->generic.data_count_bit < TOYOTA_C_BITS) { + FURI_LOG_D(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + } if(ret == SubGhzProtocolStatusOk) { inst->hop = (uint32_t)(inst->generic.data >> 32); @@ -700,7 +927,9 @@ SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize( inst->generic.serial = inst->serial; inst->generic.btn = inst->button; - inst->variant = (inst->generic.cnt != 0) ? 1 : 0; + /* Persisted cnt: 0=Corolla(A), 1=Tundra(B), 2=Prius(C). Preserve 2. */ + inst->variant = (inst->generic.cnt == 2) ? 2 : + ((inst->generic.cnt != 0) ? 1 : 0); inst->generic.cnt = inst->variant; } @@ -717,7 +946,8 @@ void subghz_protocol_decoder_toyota_get_string(void* context, FuriString* output uint32_t serial = (uint32_t)((inst->generic.data >> 4) & 0x0FFFFFFF); uint8_t button = (uint8_t)(inst->generic.data & 0x0F); - uint8_t var = (inst->generic.cnt != 0) ? 1 : 0; + uint8_t var = (inst->generic.cnt == 2) ? 2 : + ((inst->generic.cnt != 0) ? 1 : 0); furi_string_cat_printf( output, diff --git a/lib/toolbox/SConscript b/lib/toolbox/SConscript index 660d8380..bb62c58b 100644 --- a/lib/toolbox/SConscript +++ b/lib/toolbox/SConscript @@ -34,6 +34,7 @@ env.Append( File("stream/file_stream.h"), File("stream/string_stream.h"), File("stream/buffered_file_stream.h"), + File("buffer_stream.h"), File("strint.h"), File("pipe.h"), File("protocols/protocol_dict.h"), diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c7d65613..fd3a7a5d 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,92.0,, +Version,+,92.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -156,6 +156,7 @@ Header,+,lib/stm32wb_hal/Inc/stm32wbxx_ll_wwdg.h,, Header,+,lib/toolbox/api_lock.h,, Header,+,lib/toolbox/args.h,, Header,+,lib/toolbox/bit_buffer.h,, +Header,+,lib/toolbox/buffer_stream.h,, Header,+,lib/toolbox/cli/cli_ansi.h,, Header,+,lib/toolbox/cli/cli_command.h,, Header,+,lib/toolbox/cli/cli_registry.h,, @@ -743,6 +744,15 @@ Function,+,bt_set_settings,void,"Bt*, const BtSettings*" Function,+,bt_set_status_changed_callback,void,"Bt*, BtStatusChangedCallback, void*" Function,+,bt_settings_load,void,BtSettings* Function,+,bt_settings_save,void,const BtSettings* +Function,+,buffer_get_data,uint8_t*,Buffer* +Function,+,buffer_get_size,size_t,Buffer* +Function,+,buffer_reset,void,Buffer* +Function,+,buffer_stream_alloc,BufferStream*,"size_t, size_t" +Function,+,buffer_stream_free,void,BufferStream* +Function,+,buffer_stream_get_overrun_count,size_t,BufferStream* +Function,+,buffer_stream_receive,Buffer*,"BufferStream*, uint32_t" +Function,+,buffer_stream_reset,void,BufferStream* +Function,+,buffer_stream_send_from_isr,_Bool,"BufferStream*, const uint8_t*, size_t" Function,+,buffered_file_stream_alloc,Stream*,Storage* Function,+,buffered_file_stream_close,_Bool,Stream* Function,+,buffered_file_stream_get_error,FS_Error,Stream*