Compare commits

..
Author SHA1 Message Date
d4rks1d33 a3698f93a9 Improvements in modulation hopping. Thanks Zero-Mega
Build Dev Firmware / build (push) Successful in 16m28s
2026-06-13 16:56:02 -03:00
d4rks1d33 94dcc82483 Official Flipper Mobile APP is now working
Build Dev Firmware / build (push) Successful in 16m58s
2026-06-12 22:36:08 -03:00
d4rks1d33 9b7499be36 Fix kia v3/v4/v5
Build Dev Firmware / build (push) Successful in 17m16s
2026-06-12 20:59:48 -03:00
d4rks1d33 018a5feb29 Add Toyota Keeloq (only decode)
Build Dev Firmware / build (push) Failing after 14m50s
2026-06-07 23:45:08 -03:00
71 changed files with 1081 additions and 5812 deletions
-2
View File
@@ -90,5 +90,3 @@ lib/subghz/protocols/honda_rolling.c
lib/subghz/protocols/honda_rolling.h
lib/subghz/protocols/honda_pandora.c
lib/subghz/protocols/honda_pandora.h
lib/subghz/protocols/toyota.c
lib/subghz/protocols/toyota.h
+34 -32
View File
@@ -1,9 +1,11 @@
#include "subghz_txrx_i.h" // IWYU pragma: keep
#include <math.h>
#include <furi_hal_subghz.h>
#include <lib/subghz/protocols/protocol_items.h>
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
#include "../../../../lib/subghz/devices/subghz_preset_delta.h"
#include <lib/subghz/blocks/custom_btn.h>
#define TAG "SubGhzTxRx"
@@ -498,6 +500,36 @@ void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
}
}
// Identify the hop index (0=AM650, 1=FM476, 2=FM95) from the name.
// Must match the order defined in subghz_preset_delta.h
static int subghz_hop_index_from_name(const char* name) {
if(strcmp(name, "AM650") == 0) return 0;
if(strcmp(name, "FM476") == 0) return 1;
if(strcmp(name, "FM95") == 0) return 2;
return -1; // is not part of the fast hopping set
}
// Applies the target preset using delta-patch (without SRES) when possible,
// or falls back to the original full reload in any other case.
static void subghz_txrx_apply_preset_fast(
SubGhzTxRx* instance,
const char* old_preset_name,
const char* preset_name) {
int from_idx = subghz_hop_index_from_name(old_preset_name);
int to_idx = subghz_hop_index_from_name(preset_name);
if(instance->radio_device_type == SubGhzRadioDeviceTypeInternal && from_idx >= 0 &&
to_idx >= 0 && from_idx != to_idx) {
// Fast path: delta-patch without SRES or full reload (only internal CC1101)
const PresetDeltaEntry* e = &preset_delta_table[from_idx][to_idx];
furi_hal_subghz_apply_preset_delta(e->delta, e->delta_len, e->needs_scal, e->pa_table);
} else {
// Fallback: original behavior (full reload)
subghz_devices_load_preset(
instance->radio_device, FuriHalSubGhzPresetCustom, instance->preset->data);
}
}
void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold) {
furi_assert(instance);
@@ -550,22 +582,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, actual_preset_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -588,22 +605,7 @@ void subghz_txrx_preset_hopper_update(SubGhzTxRx* instance, float stay_threshold
subghz_txrx_set_preset_internal(
instance, instance->preset->frequency, instance->preset_hopper_idx, 0);
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
bool new_is_am = (strstr(preset_name, "AM") != NULL);
bool modulation_changed = (old_is_am != new_is_am);
if(modulation_changed) {
subghz_devices_reset(instance->radio_device);
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
} else {
subghz_devices_load_preset(
instance->radio_device,
FuriHalSubGhzPresetCustom,
instance->preset->data);
}
subghz_txrx_apply_preset_fast(instance, old_preset_name, preset_name);
subghz_txrx_rx(instance, instance->preset->frequency);
}
@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
@@ -1,13 +0,0 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: www.buymeacoffee.com/zweiss
@@ -1,6 +0,0 @@
dist/*
.vscode
.clang-format
.editorconfig
.env
.ufbt
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Zachary Weiss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -1,65 +0,0 @@
## TODO
Known bugs:
- [X] File format issues when Track 2 data exists but Track 1 is left empty; doesn't seem to be setting the Track 2 field with anything (doesn't overwrite existing data). However, `flipper_format_read_string()` doesn't seem to return `false`. Is the bug in my code, or with `flipper_format`?
- [X] Review how it's done in [unirfremix (Sub-GHz Remote)](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/applications/main/unirfremix/unirfremix_app.c), as IIRC that can handle empty keys, despite using the `flipper_format` lib for parsing.
- [X] Attempting to play a track that doesn't have data results in a crash (as one might expect). Need to lock out users from selecting empty tracks in the config menu or do better error handling (*Doesn't crash now, but still should probably prevent users from being able to select*)
- [ ] Custom text input scene with expanded characterset (Add Manually) has odd behavior when navigating the keys near the numpad
Emulation:
- [X] Validate arha's bitmap changes, transition over to it fully
- [X] Test piezo TX (prelim tests promising)
- [ ] General code cleanup
- [X] Reverse track precompute & replay
- [ ] Parameter tuning, find best defaults, troubleshoot improperly parsed TX
- [ ] Pursue skunkworks TX improvement ideas listed below
- [ ] Remove or reimplement interpacket
- [ ] Verify `furi_delay_us` aliasing to `64us`
Scenes:
- [X] Finish emulation config scene (reverse track functionality; possibly expand settings list to include prefix/between/suffix options)
- [ ] "Edit" scene (generalize `input_value`)
- [ ] "Rename" scene (generalize `input_name`)
File management:
- [ ] Update Add Manually flow to reflect new file format (currently only sets Track 2)
- [ ] Validation of card track data?
- [ ] Parsing loaded files into human-readable fields? (would we need to specify card type to decode correctly?)
## Skunkworks ideas
Internal TX improvements:
- [ ] Attempt downstream modulation techniques in addition to upstream, like the LF RFID worker does when writing.
- [ ] Implement using the timer system, rather than direct-writing to pins
- [X] Use the NFC (HF RFID) coil instead of or in addition to the LF coil (likely unfruitful from initial tests; we can enable/disable the oscillating field, but even with transparent mode to the ST25R3916, it seems we don't get low-enough-level control to pull it high/low correctly)
- [ ] Add "subcarriers" to each half-bit transmitted (wiggle the pin high and low rapidly)
- [ ] Piezo subcarrier tests
- [ ] LF subcarrier tests
- [X] Retry NFC oscillating field?
External RX options:
1. [TTL / PS/2 mag reader connected to UART](https://www.alibaba.com/product-detail/Mini-portable-12-3-tracks-usb_60679900708.html) (bulky, harder to source, but likely easiest to read over GPIO, and means one can read all tracks)
2. Square audio jack mag reader (this may be DOA; seems like newer versions of the Square modules have some form of preprocessing that also modifies the signal, perhaps in an effort to discourage folks using their hardware independent of their software. Thanks arha for your work investigating this)
3. Some [read-head](https://www.alibaba.com/product-detail/POS-1-2-3-triple-track_60677205741.html) directly connected to GPIO, ADC'd, and parsed all on the Flipper. Likely the most compact and cheapest module option, but also would require some signal-processing cleverness.
4. USB HID input over pre-existing USB C port infeasible; seems the FZ cannot act as an HID host (MCU is the STM32WB55RGV6TR).
5. Custom USB HID host hat based on the [MAX3421E](https://www.analog.com/en/products/max3421e.html) (USB Host Controller w/ SPI), like the [Arduino USB Host Shield](https://docs.arduino.cc/retired/shields/arduino-usb-host-shield). Would be a large but worthwhile project in its own right, and would let one connect any USB HID reader they desire (or other HID devices for other projects). Suggestion credit to arha.
6. Implement a software/firmware USB host solution over GPIO like [esp32_usb_soft_host (for ESP32)](https://github.com/sdima1357/esp32_usb_soft_host) or [V-USB (for AVR)](https://www.obdev.at/products/vusb/index.html). Suggestion credit to arha. Also a massive undertaking, but valuable in and of itself.
## arha todo & notes
Attempting to exploit flipper hardware to some extent
- [X] Preprocess all MSR data into bitwise arrays, including manchester encoding.
- [ ] Feed bits from timers
- [ ] Sync to the lfrfid timer and experiment representing a field flip with a few cycles of a high frequency carrier, like the 125khz lfrfid one. Perhaps mag readers' frontends will lowpass such signals, and keep only the low frequency component, in an attempt to drown out nearby noise?
- [X] Can the CC1101 radio be used in any way? Driving it from GD0 can achieve 50us, or about 10khz. Probably more with sync/packet mode. **Currently under testing**. The signal is extra noisy with a very wide bandwidth, but, in theory, it can work
- [ ] Can the 5V pin act as a coil driver? I've read reports it can drive 0.4A, other reports it can drive 2A. It boils down to bq25896 being fast enough. Ref: bq25896_enable_otg, which will probably need bypassing kernel libs and calling furi_hal_i2c_tx/furi_hal_i2c_tx whatever calls from Cube libs.
- [ ] Investigate transparent mode on 3916
- [ ] Can the piezo be used at its resonant frequency? I've seen LF signals being emulated with [nothing but headphones](https://github.com/smre/DCF77/blob/master/DCF77.py#L124) running a subharmonic; the wheel brake on some carts seems to react to audiofreq signals (or the RF emission from driving a speaker)
## Hummus's Fork
I made this fork initially to add reading capability using UART magnetic card readers.
Things that changed in this fork:
- Added a basic card reading ability
- Added a function to parse a new MagDevice from a Card String (%Track1?;Track2;Track3?;)
- Swapped the pins between A6 to A7 on the card that I'm using, might add this to configuration scene later on
- Adapted some of the APIs to the most recent firmware changes.
@@ -1,29 +0,0 @@
# magspoof_flipper
WIP of MagSpoof for the Flipper Zero. Basic TX of saved files confirmed working against an MSR90 with an external H-bridge module mirroring Samy Kamkar's design. Sample files are included in `resources`.
RFID coil output weaker; able to be picked up/detected by more compact mag readers such as Square, but yet to have success with it being decoded/parsed properly. Additional investigation was made into alternate internal TX options (CC1101, ST25R3916, piezo); tentatively, RFID coil + speaker (`LF + P` config setting) results in the strongest internal TX tested to date but still weaker than a dedicated external module or an actual card swipe (and sounds like a dial-up modem from hell). For information on the state of internal TX &/or misc TODOs, known bugs, etc, confer `NOTES.md`.
**Disclaimer**: use responsibly, and at your own risk. ***I neither condone nor am sympathetic to malicious uses of my code.*** Please only use this with magstripe cards and mag readers you own — this is solely meant as a proof of concept for educational purposes. Similarly, if using internal TX: while in my testing, I've seen no reason to believe this could damage the RFID (or other) hardware, this is inherently driving the coil in ways it was not designed or intended for; I take no responsibility for fried/bricked Flippers.
## GPIO TX Module
For those desiring better TX than the internal RFID coil can offer, one can build the module below, consisting of an H-bridge, a capacitor, and a coil. Custom GPIO pin selection is a planned future feature.
<img src="https://user-images.githubusercontent.com/20050953/215654078-1f4b370e-21b3-4324-b63c-3bbbc643120e.png" alt="Wiring diagram" title="Wiring diagram" style="height:320px">
----
## Credits
This project interpolates work from [Samy Kamkar](https://github.com/samyk/)'s [original MagSpoof project](https://github.com/samyk/magspoof), [Alexey D. (dunaevai135)](https://github.com/dunaevai135/) & [Alexandr Yaroshevich](https://github.com/AYaro)'s [Flipper hackathon project](https://github.com/dunaevai135/flipperzero-firmware/tree/dev/applications/magspoof), and the [Flipper team](https://github.com/flipperdevices)'s [LF RFID](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/main/lfrfid) and [SubGhz](https://github.com/flipperdevices/flipperzero-firmware/tree/dev/applications/main/subghz) apps.
Many thanks to everyone who has helped in addition to those above, most notably:
- [arha](https://github.com/arha) for bitmapping work, skunkworks testing, and innumerable suggestions/ideas/feedback (now a collaborator!)
- [Zalán Kórósi (Z4urce)](https://github.com/Z4urce) for an earlier app icon
- [Salvatore Sanfilippo (antirez)](https://github.com/antirez) for bitmapping suggestions and general C wisdom
- [skotopes](https://github.com/skotopes) for RFID consultation
- [Tiernan (NVX)](https://github.com/nvx) + dlz for NFC consultation
- davethepirate for EE insight and acting as a sounding board
- [cool4uma](https://github.com/cool4uma) for their work on custom text_input scenes
- [hummusec](https://github.com/hummusec) for testing of UART RX
- [xMasterX](https://github.com/xMasterX) and [WillyJL](https://github.com/Willy-JL) for keeping the app updated across API changes while I was away!
- Everyone else I've had the pleasure of chatting with!
@@ -1,25 +0,0 @@
App(
appid="magspoof",
name="MagSpoof",
apptype=FlipperAppType.EXTERNAL,
entry_point="mag_app",
cdefines=["APP_MAG"],
requires=[
"gui",
"storage",
"notification",
"dialogs",
],
provides=[],
stack_size=6 * 1024,
order=64,
fap_icon="icons/mag_10px.png",
fap_category="GPIO",
fap_icon_assets="icons",
fap_icon_assets_symbol="mag",
fap_version=(0, 10), # major, minor
fap_description="Enables wireless transmission of magstripe data",
fap_author="Zachary Weiss",
fap_weburl="https://github.com/zacharyweiss/magspoof_flipper",
fap_file_assets="resources",
)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@@ -1,494 +0,0 @@
#include "mag_helpers.h"
#define TAG "MagHelpers"
#define ZERO_PREFIX 25 // n zeros prefix
#define ZERO_BETWEEN 53 // n zeros between tracks
#define ZERO_SUFFIX 25 // n zeros suffix
#define REPEAT_DELAY_MS 50
// bits per char on a given track
const uint8_t bitlen[] = {7, 5, 5};
// char offset by track
const int sublen[] = {32, 48, 48};
uint8_t last_value = 2;
void play_halfbit(bool value, MagState* state) {
switch(state->tx) {
case MagTxStateRFID:
furi_hal_gpio_write(&gpio_rfid_carrier_out, value);
/*furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);*/
break;
case MagTxStateGPIO:
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), value);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), !value);
break;
case MagTxStatePiezo:
furi_hal_gpio_write(&gpio_speaker, value);
/*furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(&gpio_speaker, value);*/
break;
case MagTxStateLF_P:
furi_hal_gpio_write(&gpio_rfid_carrier_out, value);
furi_hal_gpio_write(&gpio_speaker, value);
/* // Weaker but cleaner signal
if(value) {
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_delay_us(10);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(&gpio_speaker, !value);
} else {
furi_delay_us(10);
}*/
/*furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);
furi_hal_gpio_write(RFID_PIN_OUT, !value);
furi_hal_gpio_write(&gpio_speaker, !value);
furi_hal_gpio_write(RFID_PIN_OUT, value);
furi_hal_gpio_write(&gpio_speaker, value);*/
break;
case MagTxStateNFC:
// turn on for duration of half-bit? or "blip" the field on / off?
// getting nothing from the mag reader either way
//(value) ? furi_hal_nfc_ll_txrx_on() : furi_hal_nfc_ll_txrx_off();
if(last_value == 2 || value != (bool)last_value) {
//furi_hal_nfc_ll_txrx_on();
furi_delay_us(64);
//furi_hal_nfc_ll_txrx_off();
}
break;
case MagTxCC1101_434:
case MagTxCC1101_868:
if(last_value == 2 || value != (bool)last_value) {
furi_hal_gpio_write(&gpio_cc1101_g0, true);
furi_delay_us(64);
furi_hal_gpio_write(&gpio_cc1101_g0, false);
}
break;
default:
break;
}
last_value = value;
}
void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse) {
for(uint16_t i = 0; i < n_bits; i++) {
uint16_t j = (reverse) ? (n_bits - i - 1) : i;
uint8_t byte = j / 8;
uint8_t bitmask = 1 << (7 - (j % 8));
/* Bits are stored in their arrays like on a card (LSB first). This is not how usually bits are stored in a
* byte, with the MSB first. the var bitmask creates the pattern to iterate through each bit, LSB first, like so
* 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, 0x80... masking bits one by one from the current byte
*
* I've chosen this LSB approach since bits and bytes are hard enough to visualize with the 5/8 and 7/8 encoding
* MSR uses. It's a biiit more complicated to process, but visualizing it with printf or a debugger is
* infinitely easier
*
* Encoding the following pairs of 5 bits as 5/8: A1234 B1234 C1234 D1234
* using this LSB format looks like: A1234B12 34C1234D 12340000
* using the MSB format, looks like: 21B4321A D4321C43 00004321
* this means reading each byte backwards when printing/debugging, and the jumping 16 bits ahead, reading 8 more
* bits backward, jumping 16 more bits ahead.
*
* I find this much more convenient for debugging, with the tiny incovenience of reading the bits in reverse
* order. Thus, the reason for the bitmask above
*/
bool bit = !!(bits_manchester[byte] & bitmask);
// TODO: reimplement timing delays. Replace fixed furi_hal_cortex_delay_us to wait instead to a specific value
// for DWT->CYCCNT. Note timer is aliased to 64us as per
// #define FURI_HAL_CORTEX_INSTRUCTIONS_PER_MICROSECOND (SystemCoreClock / 1000000) | furi_hal_cortex.c
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
// if (i % 2 == 1) furi_delay_us(state->us_interpacket);
}
}
void tx_init_rfid() {
// initialize RFID system for TX
furi_hal_ibutton_pin_configure();
// furi_hal_ibutton_start_drive();
furi_hal_ibutton_pin_write(false);
// Initializing at GpioSpeedLow seems sufficient for our needs; no improvements seen by increasing speed state
// this doesn't seem to make a difference, leaving it in
furi_hal_gpio_init(&gpio_rfid_data_in, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_rfid_data_in, false);
// false->ground RFID antenna; true->don't ground
// skotopes (RFID dev) say normally you'd want RFID_PULL in high for signal forming, while modulating RFID_OUT
// dunaevai135 had it low in their old code. Leaving low, as it doesn't seem to make a difference on my janky antenna
furi_hal_gpio_init(&gpio_nfc_irq_rfid_pull, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_write(&gpio_nfc_irq_rfid_pull, false);
furi_hal_gpio_init(&gpio_rfid_carrier_out, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_delay_ms(300);
}
void tx_deinit_rfid() {
// reset RFID system
furi_hal_gpio_write(&gpio_rfid_carrier_out, 0);
furi_hal_rfid_pins_reset();
}
void tx_init_rf(int hz) {
// presets and frequency will need some experimenting
furi_hal_subghz_reset();
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_set_frequency_and_path(hz);
furi_hal_subghz_tx();
furi_hal_gpio_write(&gpio_cc1101_g0, false);
}
void tx_init_piezo() {
// TODO: some special mutex acquire procedure? c.f. furi_hal_speaker.c
furi_hal_gpio_init(&gpio_speaker, GpioModeOutputPushPull, GpioPullNo, GpioSpeedLow);
}
void tx_deinit_piezo() {
// TODO: some special mutex release procedure?
furi_hal_gpio_init(&gpio_speaker, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
bool tx_init(MagState* state) {
// Initialize configured TX method
switch(state->tx) {
case MagTxStateRFID:
tx_init_rfid();
break;
case MagTxStateGPIO:
// gpio_item_configure_all_pins(GpioModeOutputPushPull);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_input),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_output),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_enable),
GpioModeOutputPushPull,
GpioPullNo,
GpioSpeedLow);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 1);
// had some issues with ~300; bumped higher temporarily
furi_delay_ms(500);
break;
case MagTxStatePiezo:
tx_init_piezo();
break;
case MagTxStateLF_P:
tx_init_piezo();
tx_init_rfid();
break;
case MagTxStateNFC:
//furi_hal_nfc_exit_sleep();
break;
case MagTxCC1101_434:
tx_init_rf(434000000);
break;
case MagTxCC1101_868:
tx_init_rf(868000000);
break;
default:
return false;
}
return true;
}
bool tx_deinit(MagState* state) {
// Reset configured TX method
switch(state->tx) {
case MagTxStateRFID:
tx_deinit_rfid();
break;
case MagTxStateGPIO:
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_input), 0);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_output), 0);
furi_hal_gpio_write(mag_state_enum_to_pin(state->pin_enable), 0);
// set back to analog output mode? - YES
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_input), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_output), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(
mag_state_enum_to_pin(state->pin_enable), GpioModeAnalog, GpioPullNo, GpioSpeedLow);
//gpio_item_configure_all_pins(GpioModeAnalog);
break;
case MagTxStatePiezo:
tx_deinit_piezo();
break;
case MagTxStateLF_P:
tx_deinit_piezo();
tx_deinit_rfid();
break;
case MagTxStateNFC:
//furi_hal_nfc_ll_txrx_off();
//furi_hal_nfc_start_sleep();
break;
case MagTxCC1101_434:
case MagTxCC1101_868:
furi_hal_gpio_write(&gpio_cc1101_g0, false);
furi_hal_subghz_reset();
furi_hal_subghz_idle();
break;
default:
return false;
}
return true;
}
void mag_spoof(Mag* mag) {
MagState* state = &mag->state;
// TODO: cleanup this section. Possibly move precompute + tx_init to emulate_on_enter?
FuriString* ft1 = mag->mag_dev->dev_data.track[0].str;
FuriString* ft2 = mag->mag_dev->dev_data.track[1].str;
FuriString* ft3 = mag->mag_dev->dev_data.track[2].str;
char *data1, *data2, *data3;
data1 = malloc(furi_string_size(ft1) + 1);
data2 = malloc(furi_string_size(ft2) + 1);
data3 = malloc(furi_string_size(ft3) + 1);
strncpy(data1, furi_string_get_cstr(ft1), furi_string_size(ft1));
strncpy(data2, furi_string_get_cstr(ft2), furi_string_size(ft2));
strncpy(data3, furi_string_get_cstr(ft3), furi_string_size(ft3));
if(furi_log_get_level() >= FuriLogLevelDebug) {
debug_mag_string(data1, bitlen[0], sublen[0]);
debug_mag_string(data2, bitlen[1], sublen[1]);
debug_mag_string(data3, bitlen[2], sublen[2]);
}
uint8_t bits_t1_raw[64] = {0x00}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_t1_manchester[128] = {0x00}; // twice the above
uint16_t bits_t1_count = mag_encode(
data1, (uint8_t*)bits_t1_manchester, (uint8_t*)bits_t1_raw, bitlen[0], sublen[0]);
uint8_t bits_t2_raw[64] = {0x00}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_t2_manchester[128] = {0x00}; // twice the above
uint16_t bits_t2_count = mag_encode(
data2, (uint8_t*)bits_t2_manchester, (uint8_t*)bits_t2_raw, bitlen[1], sublen[1]);
uint8_t bits_t3_raw[64] = {0x00};
uint8_t bits_t3_manchester[128] = {0x00};
uint16_t bits_t3_count = mag_encode(
data3, (uint8_t*)bits_t3_manchester, (uint8_t*)bits_t3_raw, bitlen[2], sublen[2]);
if(furi_log_get_level() >= FuriLogLevelDebug) {
printf(
"Manchester bitcount: T1: %d, T2: %d, T3: %d\r\n",
bits_t1_count,
bits_t2_count,
bits_t3_count);
printf("T1 raw: ");
for(int i = 0; i < bits_t1_count / 16; i++) printf("%02x ", bits_t1_raw[i]);
printf("\r\nT1 manchester: ");
for(int i = 0; i < bits_t1_count / 8; i++) printf("%02x ", bits_t1_manchester[i]);
printf("\r\nT2 raw: ");
for(int i = 0; i < bits_t2_count / 16; i++) printf("%02x ", bits_t2_raw[i]);
printf("\r\nT2 manchester: ");
for(int i = 0; i < bits_t2_count / 8; i++) printf("%02x ", bits_t2_manchester[i]);
printf("\r\nT3 raw: ");
for(int i = 0; i < bits_t3_count / 16; i++) printf("%02x ", bits_t3_raw[i]);
printf("\r\nT3 manchester: ");
for(int i = 0; i < bits_t3_count / 8; i++) printf("%02x ", bits_t3_manchester[i]);
printf("\r\nBitwise emulation done\r\n\r\n");
}
last_value = 2;
bool bit = false;
if(!tx_init(state)) return;
uint8_t i = 0;
do {
FURI_CRITICAL_ENTER();
for(uint16_t i = 0; i < (ZERO_PREFIX * 2); i++) {
// is this right?
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateOne))
play_track((uint8_t*)bits_t1_manchester, bits_t1_count, state, false);
if((state->track == MagTrackStateOneAndTwo))
for(uint16_t i = 0; i < (ZERO_BETWEEN * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
if((state->track == MagTrackStateOneAndTwo) || (state->track == MagTrackStateTwo))
play_track(
(uint8_t*)bits_t2_manchester,
bits_t2_count,
state,
(state->reverse == MagReverseStateOn));
if((state->track == MagTrackStateThree))
play_track((uint8_t*)bits_t3_manchester, bits_t3_count, state, false);
for(uint16_t i = 0; i < (ZERO_SUFFIX * 2); i++) {
if(!!(i % 2)) bit ^= 1;
play_halfbit(bit, state);
furi_delay_us(state->us_clock);
}
FURI_CRITICAL_EXIT();
i++;
FURI_LOG_D(
TAG, "TX %u (n_repeats: %u, repeat_mode: %u)", i, state->n_repeats, state->repeat_mode);
furi_delay_ms(REPEAT_DELAY_MS);
} while((i < state->n_repeats) && state->repeat_mode);
free(data1);
free(data2);
free(data3);
tx_deinit(state);
}
uint16_t add_bit(bool value, uint8_t* out, uint16_t count) {
uint8_t bit = count % 8;
uint8_t byte = count / 8;
if(value) {
out[byte] |= 0x01;
}
if(bit < 7) out[byte] <<= 1;
return count + 1;
}
uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count) {
static bool toggle = 0;
toggle ^= 0x01;
count = add_bit(toggle, out, count);
if(value) toggle ^= 0x01;
count = add_bit(toggle, out, count);
return count;
}
uint16_t mag_encode(
char* data,
uint8_t* out_manchester,
uint8_t* out_raw,
uint8_t track_bits,
uint8_t track_ascii_offset) {
/*
* track_bits - the number of raw (data) bits on the track. on ISO cards, that's 7 for track 1, or 5 for 2/3 - this is samy's bitlen
* - this count includes the parity bit
* track_ascii_offset - how much the ascii values are offset. track 1 makes space (ascii 32) become data 0x00,
* - tracks 2/3 make ascii "0" become data 0x00 - this is samy's sublen
*
*/
uint16_t raw_bits_count = 0;
uint16_t output_count = 0;
int tmp, crc, lrc = 0;
/* // why are we adding zeros to the encoded string if we're also doing it while playing?
for(int i = 0; i < ZERO_PREFIX; i++) {
output_count = add_bit_manchester(0, out_manchester, output_count);
raw_bits_count = add_bit(0, out_raw, raw_bits_count);
}*/
for(int i = 0; *(data + i) != 0; i++) {
crc = 1;
tmp = *(data + i) - track_ascii_offset;
for(int j = 0; j < track_bits - 1; j++) {
crc ^= tmp & 1;
lrc ^= (tmp & 1) << j;
raw_bits_count = add_bit(tmp & 0x01, out_raw, raw_bits_count);
output_count = add_bit_manchester(tmp & 0x01, out_manchester, output_count);
tmp >>= 1;
}
raw_bits_count = add_bit(crc, out_raw, raw_bits_count);
output_count = add_bit_manchester(crc, out_manchester, output_count);
}
// LRC byte
tmp = lrc;
crc = 1;
for(int j = 0; j < track_bits - 1; j++) {
crc ^= tmp & 0x01;
raw_bits_count = add_bit(tmp & 0x01, out_raw, raw_bits_count);
output_count = add_bit_manchester(tmp & 0x01, out_manchester, output_count);
tmp >>= 1;
}
raw_bits_count = add_bit(crc, out_raw, raw_bits_count);
output_count = add_bit_manchester(crc, out_manchester, output_count);
return output_count;
}
void debug_mag_string(char* data, uint8_t track_bits, uint8_t track_ascii_offset) {
uint8_t bits_raw[64] = {0}; // 68 chars max track 1 + 1 char crc * 7 approx =~ 483 bits
uint8_t bits_manchester[128] = {0}; // twice the above
int numbits = 0;
printf("Encoding [%s] with %d bits\r\n", data, track_bits);
numbits = mag_encode(
data, (uint8_t*)bits_manchester, (uint8_t*)bits_raw, track_bits, track_ascii_offset);
printf("Got %d bits\r\n", numbits);
printf("Raw byte stream: ");
for(int i = 0; i < numbits / 8 / 2; i++) {
printf("%02x", bits_raw[i]);
if(i % 4 == 3) printf(" ");
}
printf("\r\n");
printf("Bits ");
int space_counter = 0;
for(int i = 0; i < numbits / 2; i++) {
/*if(i < ZERO_PREFIX) {
printf("X");
continue;
} else if(i == ZERO_PREFIX) {
printf(" ");
space_counter = 0;
}*/
printf("%01x", (bits_raw[i / 8] & (1 << (7 - (i % 8)))) != 0);
if((space_counter) % track_bits == track_bits - 1) printf(" ");
space_counter++;
}
printf("\r\n");
printf("Manchester encoded, byte stream: ");
for(int i = 0; i < numbits / 8; i++) {
printf("%02x", bits_manchester[i]);
if(i % 4 == 3) printf(" ");
}
printf("\r\n\r\n");
}
@@ -1,25 +0,0 @@
#include "../mag_i.h"
#include <stdio.h>
#include <string.h>
void play_halfbit(bool value, MagState* state);
void play_track(uint8_t* bits_manchester, uint16_t n_bits, MagState* state, bool reverse);
void tx_init_rf(int hz);
void tx_init_rfid();
void tx_init_piezo();
bool tx_init(MagState* state);
void tx_deinit_piezo();
void tx_deinit_rfid();
bool tx_deinit(MagState* state);
uint16_t add_bit(bool value, uint8_t* out, uint16_t count);
uint16_t add_bit_manchester(bool value, uint8_t* out, uint16_t count);
uint16_t mag_encode(
char* data,
uint8_t* out_manchester,
uint8_t* out_raw,
uint8_t track_bits,
uint8_t track_ascii_offset);
void debug_mag_string(char* data, uint8_t track_bits, uint8_t track_ascii_offset);
void mag_spoof(Mag* mag);
@@ -1,583 +0,0 @@
#include "mag_text_input.h"
#include <gui/elements.h>
#include <assets_icons.h>
#include <furi.h>
struct Mag_TextInput {
View* view;
FuriTimer* timer;
};
typedef struct {
const char text;
const uint8_t x;
const uint8_t y;
} Mag_TextInputKey;
typedef struct {
const char* header;
char* text_buffer;
size_t text_buffer_size;
bool clear_default_text;
Mag_TextInputCallback callback;
void* callback_context;
uint8_t selected_row;
uint8_t selected_column;
// Mag_TextInputValidatorCallback validator_callback;
// void* validator_callback_context;
// FuriString* validator_text;
// bool validator_message_visible;
} Mag_TextInputModel;
static const uint8_t keyboard_origin_x = 1;
static const uint8_t keyboard_origin_y = 29;
static const uint8_t keyboard_row_count = 3;
#define ENTER_KEY '\r'
#define BACKSPACE_KEY '\b'
static const Mag_TextInputKey keyboard_keys_row_1[] = {
{'q', 1, 8},
{'w', 9, 8},
{'e', 17, 8},
{'r', 25, 8},
{'t', 33, 8},
{'y', 41, 8},
{'u', 49, 8},
{'i', 57, 8},
{'o', 65, 8},
{'p', 73, 8},
{'0', 81, 8},
{'1', 89, 8},
{'2', 97, 8},
{'3', 105, 8},
{'%', 113, 8},
{'^', 120, 8},
};
static const Mag_TextInputKey keyboard_keys_row_2[] = {
{'a', 1, 20},
{'s', 9, 20},
{'d', 18, 20},
{'f', 25, 20},
{'g', 33, 20},
{'h', 41, 20},
{'j', 49, 20},
{'k', 57, 20},
{'l', 65, 20},
{BACKSPACE_KEY, 72, 12},
{'4', 89, 20},
{'5', 97, 20},
{'6', 105, 20},
{'/', 113, 20},
{'?', 120, 20},
};
static const Mag_TextInputKey keyboard_keys_row_3[] = {
{'z', 1, 32},
{'x', 9, 32},
{'c', 18, 32},
{'v', 25, 32},
{'b', 33, 32},
{'n', 41, 32},
{'m', 49, 32},
{'_', 57, 32},
{ENTER_KEY, 64, 23},
{'7', 89, 32},
{'8', 97, 32},
{'9', 105, 32},
{';', 113, 32},
{'=', 120, 32},
};
static uint8_t get_row_size(uint8_t row_index) {
uint8_t row_size = 0;
switch(row_index + 1) {
case 1:
row_size = sizeof(keyboard_keys_row_1) / sizeof(Mag_TextInputKey);
break;
case 2:
row_size = sizeof(keyboard_keys_row_2) / sizeof(Mag_TextInputKey);
break;
case 3:
row_size = sizeof(keyboard_keys_row_3) / sizeof(Mag_TextInputKey);
break;
}
return row_size;
}
static const Mag_TextInputKey* get_row(uint8_t row_index) {
const Mag_TextInputKey* row = NULL;
switch(row_index + 1) {
case 1:
row = keyboard_keys_row_1;
break;
case 2:
row = keyboard_keys_row_2;
break;
case 3:
row = keyboard_keys_row_3;
break;
}
return row;
}
static char get_selected_char(Mag_TextInputModel* model) {
return get_row(model->selected_row)[model->selected_column].text;
}
static bool char_is_lowercase(char letter) {
return (letter >= 0x61 && letter <= 0x7A);
}
static char char_to_uppercase(const char letter) {
if(letter == '_') {
return 0x20;
} else if(isalpha(letter)) {
return (letter - 0x20);
} else {
return letter;
}
}
static void mag_text_input_backspace_cb(Mag_TextInputModel* model) {
uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
if(text_length > 0) {
model->text_buffer[text_length - 1] = 0;
}
}
static void mag_text_input_view_draw_callback(Canvas* canvas, void* _model) {
Mag_TextInputModel* model = _model;
// uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
uint8_t needed_string_width = canvas_width(canvas) - 8;
uint8_t start_pos = 4;
const char* text = model->text_buffer;
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
canvas_draw_str(canvas, 2, 8, model->header);
elements_slightly_rounded_frame(canvas, 1, 12, 126, 15);
if(canvas_string_width(canvas, text) > needed_string_width) {
canvas_draw_str(canvas, start_pos, 22, "...");
start_pos += 6;
needed_string_width -= 8;
}
while(text != 0 && canvas_string_width(canvas, text) > needed_string_width) {
text++;
}
if(model->clear_default_text) {
elements_slightly_rounded_box(
canvas, start_pos - 1, 14, canvas_string_width(canvas, text) + 2, 10);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 1, 22, "|");
canvas_draw_str(canvas, start_pos + canvas_string_width(canvas, text) + 2, 22, "|");
}
canvas_draw_str(canvas, start_pos, 22, text);
canvas_set_font(canvas, FontKeyboard);
for(uint8_t row = 0; row <= keyboard_row_count; row++) {
const uint8_t column_count = get_row_size(row);
const Mag_TextInputKey* keys = get_row(row);
for(size_t column = 0; column < column_count; column++) {
if(keys[column].text == ENTER_KEY) {
canvas_set_color(canvas, ColorBlack);
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySaveSelected_24x11);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeySave_24x11);
}
} else if(keys[column].text == BACKSPACE_KEY) {
canvas_set_color(canvas, ColorBlack);
if(model->selected_row == row && model->selected_column == column) {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspaceSelected_16x9);
} else {
canvas_draw_icon(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
&I_KeyBackspace_16x9);
}
} else {
if(model->selected_row == row && model->selected_column == column) {
canvas_set_color(canvas, ColorBlack);
canvas_draw_box(
canvas,
keyboard_origin_x + keys[column].x - 1,
keyboard_origin_y + keys[column].y - 8,
7,
10);
canvas_set_color(canvas, ColorWhite);
} else {
canvas_set_color(canvas, ColorBlack);
}
if(model->clear_default_text || (char_is_lowercase(keys[column].text))) {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
char_to_uppercase(keys[column].text));
//keys[column].text);
} else {
canvas_draw_glyph(
canvas,
keyboard_origin_x + keys[column].x,
keyboard_origin_y + keys[column].y,
keys[column].text);
}
}
}
}
/*if(model->validator_message_visible) {
canvas_set_font(canvas, FontSecondary);
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_45x42);
canvas_draw_rframe(canvas, 8, 8, 112, 50, 3);
canvas_draw_rframe(canvas, 9, 9, 110, 48, 2);
elements_multiline_text(canvas, 62, 20, furi_string_get_cstr(model->validator_text));
canvas_set_font(canvas, FontKeyboard);
}*/
}
static void mag_text_input_handle_up(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_row > 0) {
model->selected_row--;
if(model->selected_column > get_row_size(model->selected_row) - 6) {
model->selected_column = model->selected_column + 1;
}
}
}
static void mag_text_input_handle_down(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_row < keyboard_row_count - 1) {
model->selected_row++;
if(model->selected_column > get_row_size(model->selected_row) - 4) {
model->selected_column = model->selected_column - 1;
}
}
}
static void mag_text_input_handle_left(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_column > 0) {
model->selected_column--;
} else {
model->selected_column = get_row_size(model->selected_row) - 1;
}
}
static void mag_text_input_handle_right(Mag_TextInput* mag_text_input, Mag_TextInputModel* model) {
UNUSED(mag_text_input);
if(model->selected_column < get_row_size(model->selected_row) - 1) {
model->selected_column++;
} else {
model->selected_column = 0;
}
}
static void
mag_text_input_handle_ok(Mag_TextInput* mag_text_input, Mag_TextInputModel* model, bool shift) {
UNUSED(mag_text_input);
char selected = get_selected_char(model);
uint8_t text_length = strlen(model->text_buffer);
if(shift) {
selected = char_to_uppercase(selected);
}
if(selected == ENTER_KEY) {
/*if(model->validator_callback &&
(!model->validator_callback(
model->text_buffer, model->validator_text, model->validator_callback_context))) {
model->validator_message_visible = true;
furi_timer_start(mag_text_input->timer, furi_kernel_get_tick_frequency() * 4);
} else*/
if(model->callback != 0 && text_length > 0) {
model->callback(model->callback_context);
}
} else if(selected == BACKSPACE_KEY) {
mag_text_input_backspace_cb(model);
} else {
if(model->clear_default_text) {
text_length = 0;
}
if(text_length < (model->text_buffer_size - 1)) {
if(char_is_lowercase(selected)) {
selected = char_to_uppercase(selected);
}
model->text_buffer[text_length] = selected;
model->text_buffer[text_length + 1] = 0;
}
}
model->clear_default_text = false;
}
static bool mag_text_input_view_input_callback(InputEvent* event, void* context) {
Mag_TextInput* mag_text_input = context;
furi_assert(mag_text_input);
bool consumed = false;
// Acquire model
Mag_TextInputModel* model = view_get_model(mag_text_input->view);
/* if((!(event->type == InputTypePress) && !(event->type == InputTypeRelease)) &&
model->validator_message_visible) {
model->validator_message_visible = false;
consumed = true;
} else*/
if(event->type == InputTypeShort) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyOk:
mag_text_input_handle_ok(mag_text_input, model, false);
break;
default:
consumed = false;
break;
}
} else if(event->type == InputTypeLong) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyOk:
mag_text_input_handle_ok(mag_text_input, model, true);
break;
case InputKeyBack:
mag_text_input_backspace_cb(model);
break;
default:
consumed = false;
break;
}
} else if(event->type == InputTypeRepeat) {
consumed = true;
switch(event->key) {
case InputKeyUp:
mag_text_input_handle_up(mag_text_input, model);
break;
case InputKeyDown:
mag_text_input_handle_down(mag_text_input, model);
break;
case InputKeyLeft:
mag_text_input_handle_left(mag_text_input, model);
break;
case InputKeyRight:
mag_text_input_handle_right(mag_text_input, model);
break;
case InputKeyBack:
mag_text_input_backspace_cb(model);
break;
default:
consumed = false;
break;
}
}
// Commit model
view_commit_model(mag_text_input->view, consumed);
return consumed;
}
void mag_text_input_timer_callback(void* context) {
furi_assert(context);
Mag_TextInput* mag_text_input = context;
UNUSED(mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ model->validator_message_visible = false; },
true);*/
}
Mag_TextInput* mag_text_input_alloc() {
Mag_TextInput* mag_text_input = malloc(sizeof(Mag_TextInput));
mag_text_input->view = view_alloc();
view_set_context(mag_text_input->view, mag_text_input);
view_allocate_model(mag_text_input->view, ViewModelTypeLocking, sizeof(Mag_TextInputModel));
view_set_draw_callback(mag_text_input->view, mag_text_input_view_draw_callback);
view_set_input_callback(mag_text_input->view, mag_text_input_view_input_callback);
mag_text_input->timer =
furi_timer_alloc(mag_text_input_timer_callback, FuriTimerTypeOnce, mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ model->validator_text = furi_string_alloc(); },
false);*/
mag_text_input_reset(mag_text_input);
return mag_text_input;
}
void mag_text_input_free(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
/*with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ furi_string_free(model->validator_text); },
false);*/
// Send stop command
furi_timer_stop(mag_text_input->timer);
// Release allocated memory
furi_timer_free(mag_text_input->timer);
view_free(mag_text_input->view);
free(mag_text_input);
}
void mag_text_input_reset(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->text_buffer_size = 0;
model->header = "";
model->selected_row = 0;
model->selected_column = 0;
model->clear_default_text = false;
model->text_buffer = NULL;
model->text_buffer_size = 0;
model->callback = NULL;
model->callback_context = NULL;
/*model->validator_callback = NULL;
model->validator_callback_context = NULL;
furi_string_reset(model->validator_text);
model->validator_message_visible = false;*/
},
true);
}
View* mag_text_input_get_view(Mag_TextInput* mag_text_input) {
furi_assert(mag_text_input);
return mag_text_input->view;
}
void mag_text_input_set_result_callback(
Mag_TextInput* mag_text_input,
Mag_TextInputCallback callback,
void* callback_context,
char* text_buffer,
size_t text_buffer_size,
bool clear_default_text) {
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->callback = callback;
model->callback_context = callback_context;
model->text_buffer = text_buffer;
model->text_buffer_size = text_buffer_size;
model->clear_default_text = clear_default_text;
if(text_buffer && text_buffer[0] != '\0') {
// Set focus on Save
model->selected_row = 2;
model->selected_column = 8;
}
},
true);
}
/* void mag_text_input_set_validator(
Mag_TextInput* mag_text_input,
Mag_TextInputValidatorCallback callback,
void* callback_context) {
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{
model->validator_callback = callback;
model->validator_callback_context = callback_context;
},
true);
}
Mag_TextInputValidatorCallback
mag_text_input_get_validator_callback(Mag_TextInput* mag_text_input) {
Mag_TextInputValidatorCallback validator_callback = NULL;
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ validator_callback = model->validator_callback; },
false);
return validator_callback;
}
void* mag_text_input_get_validator_callback_context(Mag_TextInput* mag_text_input) {
void* validator_callback_context = NULL;
with_view_model(
mag_text_input->view,
Mag_TextInputModel * model,
{ validator_callback_context = model->validator_callback_context; },
false);
return validator_callback_context;
}*/
void mag_text_input_set_header_text(Mag_TextInput* mag_text_input, const char* text) {
with_view_model(
mag_text_input->view, Mag_TextInputModel * model, { model->header = text; }, true);
}
@@ -1,82 +0,0 @@
#pragma once
#include <gui/view.h>
// #include "mag_validators.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Text input anonymous structure */
typedef struct Mag_TextInput Mag_TextInput;
typedef void (*Mag_TextInputCallback)(void* context);
// typedef bool (*Mag_TextInputValidatorCallback)(const char* text, FuriString* error, void* context);
/** Allocate and initialize text input
*
* This text input is used to enter string
*
* @return Mag_TextInput instance
*/
Mag_TextInput* mag_text_input_alloc();
/** Deinitialize and free text input
*
* @param mag_text_input Mag_TextInput instance
*/
void mag_text_input_free(Mag_TextInput* mag_text_input);
/** Clean text input view Note: this function does not free memory
*
* @param mag_text_input Text input instance
*/
void mag_text_input_reset(Mag_TextInput* mag_text_input);
/** Get text input view
*
* @param mag_text_input Mag_TextInput instance
*
* @return View instance that can be used for embedding
*/
View* mag_text_input_get_view(Mag_TextInput* mag_text_input);
/** Set text input result callback
*
* @param mag_text_input Mag_TextInput instance
* @param callback callback fn
* @param callback_context callback context
* @param text_buffer pointer to YOUR text buffer, that we going
* to modify
* @param text_buffer_size YOUR text buffer size in bytes. Max string
* length will be text_buffer_size-1.
* @param clear_default_text clear text from text_buffer on first OK
* event
*/
void mag_text_input_set_result_callback(
Mag_TextInput* mag_text_input,
Mag_TextInputCallback callback,
void* callback_context,
char* text_buffer,
size_t text_buffer_size,
bool clear_default_text);
/* void mag_text_input_set_validator(
Mag_TextInput* mag_text_input,
Mag_TextInputValidatorCallback callback,
void* callback_context);
Mag_TextInputValidatorCallback
mag_text_input_get_validator_callback(Mag_TextInput* mag_text_input);
void* mag_text_input_get_validator_callback_context(Mag_TextInput* mag_text_input); */
/** Set text input header text
*
* @param mag_text_input Mag_TextInput instance
* @param text text to be shown
*/
void mag_text_input_set_header_text(Mag_TextInput* mag_text_input, const char* text);
#ifdef __cplusplus
}
#endif
@@ -1,67 +0,0 @@
#pragma once
#define MAG_VERSION_APP FAP_VERSION
#define MAG_DEVELOPER "Zachary Weiss"
#define MAG_GITHUB "github.com/zacharyweiss/magspoof_flipper"
typedef enum {
MagReverseStateOff,
MagReverseStateOn,
} MagReverseState;
typedef enum {
MagTrackStateOneAndTwo,
MagTrackStateOne,
MagTrackStateTwo,
MagTrackStateThree,
} MagTrackState;
typedef enum {
MagTxStateRFID,
MagTxStateGPIO,
MagTxStatePiezo,
MagTxStateLF_P, // combo of RFID and Piezo
MagTxStateNFC,
MagTxCC1101_434,
MagTxCC1101_868,
} MagTxState;
typedef enum {
MagPinA7,
MagPinA6,
MagPinA4,
MagPinB3,
MagPinB2,
MagPinC3,
MagPinC1,
MagPinC0,
} MagPin;
#define MAG_STATE_DEFAULT_REVERSE MagReverseStateOff
#define MAG_STATE_DEFAULT_TRACK MagTrackStateOneAndTwo
#define MAG_STATE_DEFAULT_TX MagTxStateGPIO
#define MAG_STATE_DEFAULT_US_CLOCK 240
#define MAG_STATE_DEFAULT_US_INTERPACKET 10
#define MAG_STATE_DEFAULT_PIN_INPUT MagPinA7
#define MAG_STATE_DEFAULT_PIN_OUTPUT MagPinA6
#define MAG_STATE_DEFAULT_PIN_ENABLE MagPinA4
#define MAG_STATE_DEFAULT_ALLOW_UART false
#define MAG_STATE_DEFAULT_N_REPEATS 3
#define MAG_STATE_DEFAULT_REPEAT_MODE true
typedef enum {
MagViewSubmenu,
MagViewDialogEx,
MagViewPopup,
MagViewLoading,
MagViewWidget,
MagViewVariableItemList,
MagViewTextInput,
MagViewTextMagInput,
} MagView;
typedef enum {
UART_TerminalEventRefreshConsoleOutput = 0,
UART_TerminalEventStartConsole,
UART_TerminalEventStartKeyboard,
} UART_TerminalCustomEvent;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

-268
View File
@@ -1,268 +0,0 @@
#include "mag_i.h"
#define TAG "Mag"
static bool mag_debug_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);
Mag* mag = context;
return scene_manager_handle_custom_event(mag->scene_manager, event);
}
static bool mag_debug_back_event_callback(void* context) {
furi_assert(context);
Mag* mag = context;
return scene_manager_handle_back_event(mag->scene_manager);
}
static Mag* mag_alloc() {
Mag* mag = malloc(sizeof(Mag));
mag->storage = furi_record_open(RECORD_STORAGE);
mag->dialogs = furi_record_open(RECORD_DIALOGS);
mag->file_name = furi_string_alloc();
mag->file_path = furi_string_alloc_set(MAG_APP_FOLDER);
mag->args = furi_string_alloc();
mag->view_dispatcher = view_dispatcher_alloc();
mag->scene_manager = scene_manager_alloc(&mag_scene_handlers, mag);
view_dispatcher_set_event_callback_context(mag->view_dispatcher, mag);
view_dispatcher_set_custom_event_callback(
mag->view_dispatcher, mag_debug_custom_event_callback);
view_dispatcher_set_navigation_event_callback(
mag->view_dispatcher, mag_debug_back_event_callback);
mag->mag_dev = mag_device_alloc();
mag_state_load(&mag->state);
// Open GUI record
mag->gui = furi_record_open(RECORD_GUI);
// Open Notification record
mag->notifications = furi_record_open(RECORD_NOTIFICATION);
// Submenu
mag->submenu = submenu_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewSubmenu, submenu_get_view(mag->submenu));
// Popup
mag->popup = popup_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewPopup, popup_get_view(mag->popup));
// Loading
mag->loading = loading_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewLoading, loading_get_view(mag->loading));
// Widget
mag->widget = widget_alloc();
view_dispatcher_add_view(mag->view_dispatcher, MagViewWidget, widget_get_view(mag->widget));
// Variable Item List
mag->variable_item_list = variable_item_list_alloc();
view_dispatcher_add_view(
mag->view_dispatcher,
MagViewVariableItemList,
variable_item_list_get_view(mag->variable_item_list));
// Text Input
mag->text_input = text_input_alloc();
view_dispatcher_add_view(
mag->view_dispatcher, MagViewTextInput, text_input_get_view(mag->text_input));
// Custom Mag Text Input
mag->mag_text_input = mag_text_input_alloc();
view_dispatcher_add_view(
mag->view_dispatcher, MagViewTextMagInput, mag_text_input_get_view(mag->mag_text_input));
// Disable expansion protocol to avoid interference with UART Handle
mag->expansion = furi_record_open(RECORD_EXPANSION);
expansion_disable(mag->expansion);
// Move UART here? conditional upon setting?
return mag;
}
static void mag_free(Mag* mag) {
furi_assert(mag);
furi_string_free(mag->file_name);
furi_string_free(mag->file_path);
furi_string_free(mag->args);
// Mag device
mag_device_free(mag->mag_dev);
mag->mag_dev = NULL;
// Submenu
view_dispatcher_remove_view(mag->view_dispatcher, MagViewSubmenu);
submenu_free(mag->submenu);
// Popup
view_dispatcher_remove_view(mag->view_dispatcher, MagViewPopup);
popup_free(mag->popup);
// Loading
view_dispatcher_remove_view(mag->view_dispatcher, MagViewLoading);
loading_free(mag->loading);
// Widget
view_dispatcher_remove_view(mag->view_dispatcher, MagViewWidget);
widget_free(mag->widget);
// Variable Item List
view_dispatcher_remove_view(mag->view_dispatcher, MagViewVariableItemList);
variable_item_list_free(mag->variable_item_list);
// TextInput
view_dispatcher_remove_view(mag->view_dispatcher, MagViewTextInput);
text_input_free(mag->text_input);
view_dispatcher_remove_view(mag->view_dispatcher, MagViewTextMagInput);
mag_text_input_free(mag->mag_text_input);
// View Dispatcher
view_dispatcher_free(mag->view_dispatcher);
// Scene Manager
scene_manager_free(mag->scene_manager);
// GUI
furi_record_close(RECORD_GUI);
mag->gui = NULL;
// Notifications
furi_record_close(RECORD_NOTIFICATION);
mag->notifications = NULL;
// Return previous state of expansion
expansion_enable(mag->expansion);
furi_record_close(RECORD_EXPANSION);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
free(mag);
}
// entry point for app
int32_t mag_app(void* p) {
const char* args = p;
Mag* mag = mag_alloc();
if(args && strlen(args)) {
furi_string_set(mag->args, args);
}
mag_make_app_folder(mag);
mag_migrate_and_copy_files(mag);
// Enable 5v power, multiple attempts to avoid issues with power chip protection false triggering
uint8_t attempts = 0;
bool otg_was_enabled = furi_hal_power_is_otg_enabled();
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
furi_hal_power_enable_otg();
furi_delay_ms(10);
}
view_dispatcher_attach_to_gui(mag->view_dispatcher, mag->gui, ViewDispatcherTypeFullscreen);
if(furi_string_empty(mag->args)) {
scene_manager_next_scene(mag->scene_manager, MagSceneStart);
} else {
mag_device_load_data(mag->mag_dev, mag->args, true);
MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev);
if(auto_track) {
mag->state.track = auto_track;
}
scene_manager_next_scene(mag->scene_manager, MagSceneEmulate);
}
view_dispatcher_run(mag->view_dispatcher);
// Disable 5v power
if(furi_hal_power_is_otg_enabled() && !otg_was_enabled) {
furi_hal_power_disable_otg();
}
mag_free(mag);
return 0;
}
void mag_make_app_folder(Mag* mag) {
furi_assert(mag);
if(!storage_simply_mkdir(mag->storage, MAG_APP_FOLDER)) {
dialog_message_show_storage_error(mag->dialogs, "Cannot create\napp folder");
}
}
void mag_migrate_and_copy_files(Mag* mag) {
furi_assert(mag);
Storage* storage = mag->storage;
storage_common_migrate(storage, EXT_PATH("magspoof"), STORAGE_APP_DATA_PATH_PREFIX);
storage_common_migrate(storage, EXT_PATH("mag"), STORAGE_APP_DATA_PATH_PREFIX);
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_1))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_1), APP_DATA_PATH(MAG_EXAMPLE_FILE_1));
}
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_2))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_2), APP_DATA_PATH(MAG_EXAMPLE_FILE_2));
}
if(!storage_common_exists(storage, APP_DATA_PATH(MAG_EXAMPLE_FILE_3))) {
storage_common_copy(
storage, APP_ASSETS_PATH(MAG_EXAMPLE_FILE_3), APP_DATA_PATH(MAG_EXAMPLE_FILE_3));
}
}
void mag_text_store_set(Mag* mag, const char* text, ...) {
furi_assert(mag);
va_list args;
va_start(args, text);
vsnprintf(mag->text_store, MAG_TEXT_STORE_SIZE, text, args);
va_end(args);
}
void mag_text_store_clear(Mag* mag) {
furi_assert(mag);
memset(mag->text_store, 0, sizeof(mag->text_store));
}
void mag_popup_timeout_callback(void* context) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventPopupClosed);
}
void mag_widget_callback(GuiButtonType result, InputType type, void* context) {
Mag* mag = context;
if(type == InputTypeShort) {
view_dispatcher_send_custom_event(mag->view_dispatcher, result);
}
}
void mag_text_input_callback(void* context) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventNext);
}
void mag_show_loading_popup(void* context, bool show) {
Mag* mag = context;
if(show) {
// Raise timer priority so that animations can play
furi_timer_set_thread_priority(FuriTimerThreadPriorityElevated);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewLoading);
} else {
// Restore default timer priority
furi_timer_set_thread_priority(FuriTimerThreadPriorityNormal);
}
}
@@ -1,333 +0,0 @@
#include "mag_device.h"
#include <toolbox/path.h>
#include <flipper_format/flipper_format.h>
#define TAG "MagDevice"
static const char* mag_file_header = "Flipper Mag device";
static const uint32_t mag_file_version = 1;
MagDevice* mag_device_alloc() {
MagDevice* mag_dev = malloc(sizeof(MagDevice));
mag_dev->dev_data.track[0].str = furi_string_alloc();
mag_dev->dev_data.track[1].str = furi_string_alloc();
mag_dev->dev_data.track[2].str = furi_string_alloc();
mag_dev->storage = furi_record_open(RECORD_STORAGE);
mag_dev->dialogs = furi_record_open(RECORD_DIALOGS);
mag_dev->load_path = furi_string_alloc();
return mag_dev;
}
void mag_device_data_clear(MagDeviceData* dev_data) {
furi_string_reset(dev_data->track[0].str);
furi_string_reset(dev_data->track[1].str);
furi_string_reset(dev_data->track[2].str);
}
void mag_device_clear(MagDevice* mag_dev) {
furi_assert(mag_dev);
mag_device_data_clear(&mag_dev->dev_data);
memset(&mag_dev->dev_data, 0, sizeof(mag_dev->dev_data));
furi_string_reset(mag_dev->load_path);
}
void mag_device_free(MagDevice* mag_dev) {
furi_assert(mag_dev);
mag_device_clear(mag_dev);
furi_record_close(RECORD_STORAGE);
furi_record_close(RECORD_DIALOGS);
furi_string_free(mag_dev->load_path);
//furi_string_free(mag_dev->dev_data.track[0].str);
//furi_string_free(mag_dev->dev_data.track[1].str);
//furi_string_free(mag_dev->dev_data.track[2].str);
free(mag_dev);
}
void mag_device_set_name(MagDevice* mag_dev, const char* name) {
furi_assert(mag_dev);
strlcpy(mag_dev->dev_name, name, MAG_DEV_NAME_MAX_LEN);
}
static bool mag_device_save_file(
MagDevice* mag_dev,
const char* dev_name,
const char* folder,
const char* extension,
bool use_load_path) {
furi_assert(mag_dev);
bool saved = false;
FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
FuriString* temp_str;
temp_str = furi_string_alloc();
do {
if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
// Get dir name
path_extract_dirname(furi_string_get_cstr(mag_dev->load_path), temp_str);
// Create mag directory if necessary
if(!storage_simply_mkdir((mag_dev->storage), furi_string_get_cstr(temp_str))) break;
// Make path to file to be saved
furi_string_cat_printf(temp_str, "/%s%s", dev_name, extension);
} else {
// Create mag directory if necessary
if(!storage_simply_mkdir((mag_dev->storage), MAG_APP_FOLDER)) break;
// First remove mag device file if it was saved
furi_string_printf(temp_str, "%s/%s%s", folder, dev_name, extension);
}
// Open file
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
// Write header
if(!flipper_format_write_header_cstr(file, mag_file_header, mag_file_version)) break;
// Write comment
if(!flipper_format_write_comment_cstr(file, "Mag device track data")) break;
// Write data
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
furi_string_printf(temp_str, "Track %d", i + 1);
if(!flipper_format_write_string_cstr(
file,
furi_string_get_cstr(temp_str),
furi_string_get_cstr(mag_dev->dev_data.track[i].str)))
break;
}
saved = true;
} while(0);
if(!saved) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot save\nfile");
}
furi_string_free(temp_str);
flipper_format_free(file);
return saved;
}
bool mag_device_save(MagDevice* mag_dev, const char* dev_name) {
// wrapping function in the event we have multiple formats
return mag_device_save_file(mag_dev, dev_name, MAG_APP_FOLDER, MAG_APP_EXTENSION, true);
}
bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog) {
bool parsed = false;
FuriString* filename;
filename = furi_string_alloc();
path_extract_filename(path, filename, true);
strncpy(mag_dev->dev_name, furi_string_get_cstr(filename), MAG_DEV_NAME_MAX_LEN);
FlipperFormat* file = flipper_format_file_alloc(mag_dev->storage);
FuriString* temp_str;
temp_str = furi_string_alloc();
bool deprecated_version = false;
bool data_read = true;
if(mag_dev->loading_cb) {
mag_dev->loading_cb(mag_dev->loading_cb_ctx, true);
}
do {
if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break;
// Read and verify header, check file version
uint32_t version;
if(!flipper_format_read_header(file, temp_str, &version)) break;
if(furi_string_cmp_str(temp_str, mag_file_header) || (version != mag_file_version)) {
deprecated_version = true;
break;
}
// Parse data
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
furi_string_printf(temp_str, "Track %d", i + 1);
if(!flipper_format_read_string(
file, furi_string_get_cstr(temp_str), mag_dev->dev_data.track[i].str)) {
FURI_LOG_D(TAG, "Could not read track %d data", i + 1);
// TODO: smarter load handling now that it is acceptible for some tracks to be empty
data_read = false;
}
}
parsed = true;
} while(false);
if((!parsed) && (show_dialog)) {
if(deprecated_version) {
dialog_message_show_storage_error(mag_dev->dialogs, "File format\ndeprecated");
} else if(!data_read) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot read\ndata");
} else {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot parse\nfile");
}
}
furi_string_free(temp_str);
furi_string_free(filename);
flipper_format_free(file);
return parsed;
}
bool mag_file_select(MagDevice* mag_dev) {
furi_assert(mag_dev);
// Input events and views are managed by file_browser
FuriString* mag_app_folder;
mag_app_folder = furi_string_alloc_set(MAG_APP_FOLDER);
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, MAG_APP_EXTENSION, &I_mag_file_10px);
browser_options.base_path = MAG_APP_FOLDER;
bool res = dialog_file_browser_show(
mag_dev->dialogs, mag_dev->load_path, mag_app_folder, &browser_options);
furi_string_free(mag_app_folder);
if(res) {
res = mag_device_load_data(mag_dev, mag_dev->load_path, true);
if(res) {
mag_device_set_name(mag_dev, mag_dev->dev_name);
}
}
return res;
}
bool mag_device_delete(MagDevice* mag_dev, bool use_load_path) {
furi_assert(mag_dev);
bool deleted = false;
FuriString* file_path;
file_path = furi_string_alloc();
do {
// Delete original file
if(use_load_path && !furi_string_empty(mag_dev->load_path)) {
furi_string_set(file_path, mag_dev->load_path);
} else {
furi_string_printf(
file_path, "%s/%s%s", MAG_APP_FOLDER, mag_dev->dev_name, MAG_APP_EXTENSION);
}
if(!storage_simply_remove(mag_dev->storage, furi_string_get_cstr(file_path))) break;
deleted = true;
} while(false);
if(!deleted) {
dialog_message_show_storage_error(mag_dev->dialogs, "Cannot remove\nfile");
}
furi_string_free(file_path);
return deleted;
}
bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* f_card_str) {
furi_assert(mag_dev);
FURI_LOG_D(TAG, "mag_device_parse_card_string");
const char* card_str = furi_string_get_cstr(f_card_str);
FURI_LOG_D(TAG, "Parsing card string: %s", card_str);
// Track 1
const char* track1_start = strchr(card_str, '%');
if(!track1_start) {
FURI_LOG_D(TAG, "Could not find track 1 start");
return false;
}
track1_start++;
const char* track1_end = strchr(track1_start, '?');
if(!track1_end) {
FURI_LOG_D(TAG, "Could not find track 1 end");
return false;
}
size_t track1_len = track1_end - track1_start;
FURI_LOG_D(TAG, "Track 1: %.*s", track1_len, track1_start);
mag_dev->dev_data.track[0].len = track1_len;
furi_string_printf(mag_dev->dev_data.track[0].str, "%%%.*s?", track1_len, track1_start);
// Track 2
const char* track2_start = strchr(track1_end, ';');
if(!track2_start) {
FURI_LOG_D(TAG, "Could not find track 2 start");
return true;
}
track2_start++;
const char* track2_end = strchr(track2_start, '?');
if(!track2_end) {
FURI_LOG_D(TAG, "Could not find track 2 end");
return true;
}
size_t track2_len = track2_end - track2_start;
FURI_LOG_D(TAG, "Track 2: %.*s", track2_len, track2_start);
mag_dev->dev_data.track[1].len = track2_len;
furi_string_printf(mag_dev->dev_data.track[1].str, "%%%.*s?", track2_len, track2_start);
// Track 3
const char* track3_start = strchr(track2_end, ';');
if(!track3_start) {
FURI_LOG_D(TAG, "Could not find track 3 start");
return true;
}
track3_start++;
const char* track3_end = strchr(track3_start, '?');
if(!track3_end) {
FURI_LOG_D(TAG, "Could not find track 3 end");
return true;
}
size_t track3_len = track3_end - track3_start;
FURI_LOG_D(TAG, "Track 3: %.*s", track3_len, track3_start);
mag_dev->dev_data.track[2].len = track3_len;
furi_string_printf(mag_dev->dev_data.track[2].str, "%%%.*s?", track3_len, track3_start);
return true;
}
MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev) {
// messy code to quickly check which tracks are available for emulation/display
bool is_empty_t1 = furi_string_empty(mag_dev->dev_data.track[0].str);
bool is_empty_t2 = furi_string_empty(mag_dev->dev_data.track[1].str);
bool is_empty_t3 = furi_string_empty(mag_dev->dev_data.track[2].str);
if(!is_empty_t1 && !is_empty_t2) {
return MagTrackStateOneAndTwo;
} else if(!is_empty_t1) {
return MagTrackStateOne;
} else if(!is_empty_t2) {
return MagTrackStateTwo;
} else if(!is_empty_t3) {
return MagTrackStateThree;
}
// if all empty (or something wrong with the above code)
// return default value
return MAG_STATE_DEFAULT_TRACK;
}
void mag_device_set_loading_callback(
MagDevice* mag_dev,
MagLoadingCallback callback,
void* context) {
furi_assert(mag_dev);
mag_dev->loading_cb = callback;
mag_dev->loading_cb_ctx = context;
}
@@ -1,67 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <storage/storage.h>
#include <dialogs/dialogs.h>
#include "mag_icons.h"
#include "helpers/mag_types.h"
#define MAG_DEV_NAME_MAX_LEN 22
#define MAG_DEV_TRACKS 3
#define MAG_APP_FOLDER STORAGE_APP_DATA_PATH_PREFIX
#define MAG_APP_EXTENSION ".mag"
#define MAG_EXAMPLE_FILE_1 "TestMagstripe.mag"
#define MAG_EXAMPLE_FILE_2 "SamyExpiredCard.mag"
#define MAG_EXAMPLE_FILE_3 "SamyExampleImage.mag"
typedef void (*MagLoadingCallback)(void* context, bool state);
typedef struct {
FuriString* str;
size_t len;
} MagTrack;
typedef struct {
MagTrack track[MAG_DEV_TRACKS];
} MagDeviceData;
typedef struct {
Storage* storage;
DialogsApp* dialogs;
MagDeviceData dev_data;
char dev_name[MAG_DEV_NAME_MAX_LEN + 1];
FuriString* load_path;
MagLoadingCallback loading_cb;
void* loading_cb_ctx;
} MagDevice;
MagDevice* mag_device_alloc();
void mag_device_free(MagDevice* mag_dev);
void mag_device_set_name(MagDevice* mag_dev, const char* name);
bool mag_device_save(MagDevice* mag_dev, const char* dev_name);
bool mag_device_load_data(MagDevice* mag_dev, FuriString* path, bool show_dialog);
bool mag_file_select(MagDevice* mag_dev);
void mag_device_data_clear(MagDeviceData* dev_data);
void mag_device_clear(MagDevice* mag_dev);
bool mag_device_delete(MagDevice* mag_dev, bool use_load_path);
bool mag_device_parse_card_string(MagDevice* mag_dev, FuriString* card_str);
MagTrackState mag_device_autoselect_track_state(MagDevice* mag_dev);
void mag_device_set_loading_callback(
MagDevice* mag_dev,
MagLoadingCallback callback,
void* context);
@@ -1,112 +0,0 @@
#pragma once
#include "mag_device.h"
#include "mag_state.h"
//#include "helpers/mag_helpers.h"
#include "helpers/mag_text_input.h"
#include "helpers/mag_types.h"
#include <furi.h>
#include <furi_hal.h>
#include <furi/core/log.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <expansion/expansion.h>
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <notification/notification_messages.h>
#include <gui/modules/submenu.h>
#include <gui/modules/dialog_ex.h>
#include <gui/modules/popup.h>
#include <gui/modules/loading.h>
#include <gui/modules/text_input.h>
#include <gui/modules/widget.h>
#include <gui/modules/variable_item_list.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include <toolbox/path.h>
#include <toolbox/value_index.h>
#include "scenes/mag_scene.h"
#include "scenes/mag_scene_read.h"
#define MAG_TEXT_STORE_SIZE 150
// CFWs have `submenue_add_lockable_item`; OFW doesn't,
// replace with conditional submenu item
#ifdef FW_ORIGIN_Official
#define submenu_add_lockable_item( \
submenu, label, index, callback, callback_context, locked, locked_message) \
if(!locked) { \
submenu_add_item(submenu, label, index, callback, callback_context) \
}
#endif
enum MagCustomEvent {
MagEventNext = 100,
MagEventExit,
MagEventPopupClosed,
MagEventConfirmDialog,
};
typedef struct {
ViewDispatcher* view_dispatcher;
Gui* gui;
NotificationApp* notifications;
SceneManager* scene_manager;
Storage* storage;
DialogsApp* dialogs;
MagDevice* mag_dev;
char text_store[MAG_TEXT_STORE_SIZE + 1];
FuriString* file_path;
FuriString* file_name;
FuriString* args;
MagState state;
// Common views
Submenu* submenu;
Popup* popup;
Loading* loading;
TextInput* text_input;
Widget* widget;
VariableItemList* variable_item_list;
// Custom views
Mag_TextInput* mag_text_input;
// UART
Expansion* expansion;
FuriThread* uart_rx_thread;
FuriStreamBuffer* uart_rx_stream;
uint8_t uart_rx_buf[UART_RX_BUF_SIZE + 1];
void (*handle_rx_data_cb)(uint8_t* buf, size_t len, void* context);
FuriHalSerialHandle* serial_handle;
char uart_text_input_store[UART_TERMINAL_TEXT_INPUT_STORE_SIZE + 1];
FuriString* uart_text_box_store;
size_t uart_text_box_store_strlen;
} Mag;
void mag_text_store_set(Mag* mag, const char* text, ...);
void mag_text_store_clear(Mag* mag);
void mag_show_loading_popup(void* context, bool show);
void mag_make_app_folder(Mag* mag);
void mag_migrate_and_copy_files(Mag* mag);
void mag_popup_timeout_callback(void* context);
void mag_widget_callback(GuiButtonType result, InputType type, void* context);
void mag_text_input_callback(void* context);
@@ -1,147 +0,0 @@
#include "mag_state.h"
#define TAG "MagState"
const GpioPin* mag_state_enum_to_pin(MagPin pin) {
switch(pin) {
case MagPinA7:
return &gpio_ext_pa7;
case MagPinA6:
return &gpio_ext_pa6;
case MagPinA4:
return &gpio_ext_pa4;
case MagPinB3:
return &gpio_ext_pb3;
case MagPinB2:
return &gpio_ext_pb2;
case MagPinC3:
return &gpio_ext_pc3;
case MagPinC1:
return &gpio_ext_pc1;
case MagPinC0:
return &gpio_ext_pc0;
default:
return NULL;
}
}
bool mag_state_gpio_is_valid(MagState* state) {
return (state->pin_input != state->pin_output) && (state->pin_input != state->pin_enable) &&
(state->pin_enable != state->pin_output);
}
void mag_state_gpio_reset(MagState* state) {
state->pin_input = MAG_STATE_DEFAULT_PIN_INPUT;
state->pin_output = MAG_STATE_DEFAULT_PIN_OUTPUT;
state->pin_enable = MAG_STATE_DEFAULT_PIN_ENABLE;
}
bool mag_state_load(MagState* out_state) {
MagState state;
// Try to load from file
bool loaded_from_file = false;
Storage* storage = furi_record_open(RECORD_STORAGE);
if(storage_file_exists(storage, MAG_STATE_PATH)) {
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
uint32_t tmp;
FuriString* str = furi_string_alloc();
if(!flipper_format_file_open_existing(file, MAG_STATE_PATH)) break;
if(!flipper_format_read_header(file, str, &tmp)) break;
if(furi_string_cmp_str(str, MAG_STATE_HEADER)) break;
// if(tmp != MAG_STATE_VER) break;
if(!flipper_format_read_uint32(file, "pin_input", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_INPUT;
}
state.pin_input = (MagPin)tmp;
if(!flipper_format_read_uint32(file, "pin_output", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_OUTPUT;
}
state.pin_output = (MagPin)tmp;
if(!flipper_format_read_uint32(file, "pin_enable", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_PIN_ENABLE;
}
state.pin_enable = (MagPin)tmp;
if(!flipper_format_read_bool(file, "allow_uart", &state.allow_uart, 1)) {
flipper_format_rewind(file);
state.allow_uart = MAG_STATE_DEFAULT_ALLOW_UART;
}
if(!flipper_format_read_uint32(file, "n_repeats", &tmp, 1)) {
flipper_format_rewind(file);
tmp = MAG_STATE_DEFAULT_N_REPEATS;
}
state.n_repeats = (uint8_t)tmp;
if(!flipper_format_read_bool(file, "repeat_mode", &state.repeat_mode, 1)) {
flipper_format_rewind(file);
state.repeat_mode = MAG_STATE_DEFAULT_REPEAT_MODE;
}
loaded_from_file = true;
} while(0);
flipper_format_free(file);
}
furi_record_close(RECORD_STORAGE);
// If file's GPIO config is invalid (pins overlap)
// Reset to defaults
// Additionally raise message to user?
if(!mag_state_gpio_is_valid(&state)) {
mag_state_gpio_reset(&state);
}
if(!loaded_from_file) {
mag_state_gpio_reset(&state);
state.allow_uart = MAG_STATE_DEFAULT_ALLOW_UART;
state.n_repeats = MAG_STATE_DEFAULT_N_REPEATS;
state.repeat_mode = MAG_STATE_DEFAULT_REPEAT_MODE;
}
// set defaults we don't save
state.tx = MAG_STATE_DEFAULT_TX;
state.track = MAG_STATE_DEFAULT_TRACK;
state.reverse = MAG_STATE_DEFAULT_REVERSE;
state.us_clock = MAG_STATE_DEFAULT_US_CLOCK;
state.us_interpacket = MAG_STATE_DEFAULT_US_INTERPACKET;
state.is_debug = furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug);
// Copy to caller state before popping stack
memcpy(out_state, &state, sizeof(state));
return loaded_from_file;
}
void mag_state_save(MagState* state) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_simply_mkdir(storage, MAG_STATE_DIR);
FlipperFormat* file = flipper_format_file_alloc(storage);
do {
uint32_t tmp;
if(!flipper_format_file_open_always(file, MAG_STATE_PATH)) break;
if(!flipper_format_write_header_cstr(file, MAG_STATE_HEADER, MAG_STATE_VER)) break;
tmp = (uint32_t)state->pin_input;
if(!flipper_format_write_uint32(file, "pin_input", &tmp, 1)) break;
tmp = (uint32_t)state->pin_output;
if(!flipper_format_write_uint32(file, "pin_output", &tmp, 1)) break;
tmp = (uint32_t)state->pin_enable;
if(!flipper_format_write_uint32(file, "pin_enable", &tmp, 1)) break;
if(!flipper_format_write_bool(file, "allow_uart", &state->allow_uart, 1)) break;
tmp = (uint32_t)state->n_repeats;
if(!flipper_format_write_uint32(file, "n_repeats", &tmp, 1)) break;
if(!flipper_format_write_bool(file, "repeat_mode", &state->repeat_mode, 1)) break;
} while(0);
flipper_format_free(file);
furi_record_close(RECORD_STORAGE);
}
@@ -1,44 +0,0 @@
#pragma once
#include <string.h>
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include <furi_hal_resources.h>
#include <furi_hal_rtc.h>
#include <storage/storage.h>
#include <flipper_format/flipper_format.h>
#include "helpers/mag_types.h"
#define MAG_STATE_HEADER "Mag State"
#define MAG_STATE_VER 2
#define MAG_STATE_DIR STORAGE_APP_DATA_PATH_PREFIX
#define MAG_STATE_PATH MAG_STATE_DIR "/mag_state.txt"
typedef struct {
MagTxState tx;
MagTrackState track;
MagReverseState reverse;
uint32_t us_clock;
uint32_t us_interpacket;
MagPin pin_input;
MagPin pin_output;
MagPin pin_enable;
bool allow_uart;
bool is_debug;
uint8_t n_repeats;
bool repeat_mode;
} MagState;
const GpioPin* mag_state_enum_to_pin(MagPin pin);
bool mag_state_gpio_is_valid(MagState* state);
void mag_state_gpio_reset(MagState* state);
bool mag_state_load(MagState* out_state);
void mag_state_save(MagState* state);
@@ -1,7 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
# Data matching image example in Samy's repo, for ease of comparison
Track 1: %B426684131234567^LASTNAME/FIRST^YYMMSSSDDDDDDDDDDDDDDDDDDDDDDDDD?
Track 2: ;426684131234567=230188855555555555555?
Track 3:
@@ -1,7 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
# Found in samyk's MagSpoof branch f150bb783237051fba7e4e6ed96a722e542a9663; using as test data, card is long expired
Track 1: %B493173000682759^URISTA HDZ-IVAN JAVIER ^150220100234000000?
Track 2: ;493173000682759=15022100000234?
Track 3:
@@ -1,6 +0,0 @@
Filetype: Flipper Mag device
Version: 1
# Mag device track data
Track 1: %B123456781234567^LASTNAME/FIRST^YYMMSSSDDDDDDDDDDDDDDDDDDDDDDDDD?
Track 2: ;123456781234567=YYMMSSSDDDDDDDDDDDDDD?
Track 3:
@@ -1,30 +0,0 @@
#include "mag_scene.h"
// Generate scene on_enter handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
void (*const mag_on_enter_handlers[])(void*) = {
#include "mag_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_event handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
bool (*const mag_on_event_handlers[])(void* context, SceneManagerEvent event) = {
#include "mag_scene_config.h"
};
#undef ADD_SCENE
// Generate scene on_exit handlers array
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
void (*const mag_on_exit_handlers[])(void* context) = {
#include "mag_scene_config.h"
};
#undef ADD_SCENE
// Initialize scene handlers configuration structure
const SceneManagerHandlers mag_scene_handlers = {
.on_enter_handlers = mag_on_enter_handlers,
.on_event_handlers = mag_on_event_handlers,
.on_exit_handlers = mag_on_exit_handlers,
.scene_num = MagSceneNum,
};
@@ -1,29 +0,0 @@
#pragma once
#include <gui/scene_manager.h>
// Generate scene id and total number
#define ADD_SCENE(prefix, name, id) MagScene##id,
typedef enum {
#include "mag_scene_config.h"
MagSceneNum,
} MagScene;
#undef ADD_SCENE
extern const SceneManagerHandlers mag_scene_handlers;
// Generate scene on_enter handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
#include "mag_scene_config.h"
#undef ADD_SCENE
// Generate scene on_event handlers declaration
#define ADD_SCENE(prefix, name, id) \
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
#include "mag_scene_config.h"
#undef ADD_SCENE
// Generate scene on_exit handlers declaration
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
#include "mag_scene_config.h"
#undef ADD_SCENE
@@ -1,40 +0,0 @@
#include "../mag_i.h"
void mag_scene_about_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
furi_string_cat_printf(tmp_str, "Version: %s\n", MAG_VERSION_APP);
furi_string_cat_printf(tmp_str, "Developer: %s\n", MAG_DEVELOPER);
furi_string_cat_printf(tmp_str, "GitHub: %s\n\n", MAG_GITHUB);
furi_string_cat_printf(
tmp_str,
"Unfinished port of Samy Kamkar's MagSpoof. Confer GitHub for updates; in the interim, use responsibly and at your own risk.");
// TODO: Add credits
widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(tmp_str));
furi_string_free(tmp_str);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
}
bool mag_scene_about_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(event);
UNUSED(scene_manager);
return consumed;
}
void mag_scene_about_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,16 +0,0 @@
ADD_SCENE(mag, start, Start)
ADD_SCENE(mag, about, About)
ADD_SCENE(mag, read, Read)
ADD_SCENE(mag, settings, Settings)
ADD_SCENE(mag, emulate, Emulate)
ADD_SCENE(mag, emulate_config, EmulateConfig)
ADD_SCENE(mag, file_select, FileSelect)
ADD_SCENE(mag, saved_menu, SavedMenu)
ADD_SCENE(mag, saved_info, SavedInfo)
ADD_SCENE(mag, input_name, InputName)
ADD_SCENE(mag, input_value, InputValue)
ADD_SCENE(mag, save_success, SaveSuccess)
ADD_SCENE(mag, delete_success, DeleteSuccess)
ADD_SCENE(mag, delete_confirm, DeleteConfirm)
ADD_SCENE(mag, exit_confirm, ExitConfirm)
ADD_SCENE(mag, under_construction, UnderConstruction)
@@ -1,49 +0,0 @@
#include "../mag_i.h"
#include "../mag_device.h"
void mag_scene_delete_confirm_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
MagDevice* mag_dev = mag->mag_dev;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
furi_string_printf(tmp_str, "\e#Delete %s?\e#", mag_dev->dev_name);
//TODO: print concise summary of data on card? Would need to vary by card/track type
widget_add_text_box_element(
widget, 0, 0, 128, 27, AlignCenter, AlignCenter, furi_string_get_cstr(tmp_str), true);
widget_add_button_element(widget, GuiButtonTypeLeft, "Cancel", mag_widget_callback, mag);
widget_add_button_element(widget, GuiButtonTypeRight, "Delete", mag_widget_callback, mag);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_delete_confirm_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeRight) {
consumed = true;
if(mag_device_delete(mag->mag_dev, true)) {
scene_manager_next_scene(scene_manager, MagSceneDeleteSuccess);
}
} else if(event.event == GuiButtonTypeLeft) {
consumed = true;
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void mag_scene_delete_confirm_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,39 +0,0 @@
#include "../mag_i.h"
void mag_scene_delete_success_on_enter(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
popup_set_icon(popup, 0, 2, &I_DolphinMafia_115x62);
popup_set_header(popup, "Deleted", 83, 19, AlignLeft, AlignBottom);
popup_set_callback(popup, mag_popup_timeout_callback);
popup_set_context(popup, mag);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewPopup);
}
bool mag_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventPopupClosed) {
consumed = true;
scene_manager_search_and_switch_to_previous_scene(
mag->scene_manager, MagSceneFileSelect);
}
}
return consumed;
}
void mag_scene_delete_success_on_exit(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
popup_reset(popup);
}
@@ -1,93 +0,0 @@
#include "../mag_i.h"
#include "../helpers/mag_helpers.h"
#define TAG "MagSceneEmulate"
void cat_trackstr(FuriString* str, uint8_t calls, uint8_t i, FuriString* trackstr) {
furi_string_cat_printf(
str,
"%sTrack %d:%s%s\n",
(calls == 0) ? "" : "\n", // if first line, don't prepend a "\n"
(i + 1),
furi_string_empty(trackstr) ? " " : "\n",
furi_string_empty(trackstr) ? "< empty >" : furi_string_get_cstr(trackstr));
}
void mag_scene_emulate_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
// Use strlcpy instead perhaps, to truncate to screen width, then add ellipses if needed?
furi_string_printf(tmp_str, "%s\r\n", mag->mag_dev->dev_name);
// TODO: Display other relevant config settings (namely RFID vs GPIO)?
widget_add_icon_element(widget, 1, 1, &I_mag_file_10px);
widget_add_string_element(
widget, 13, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
FURI_LOG_D(TAG, "%d", mag->state.reverse);
// print relevant data
uint8_t cat_count = 0;
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
FuriString* trackstr = mag->mag_dev->dev_data.track[i].str;
// still messy / dumb way to do this, but slightly cleaner than before.
// will clean up more later
switch(mag->state.track) {
case MagTrackStateOne:
if(i == 0) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateTwo:
if(i == 1) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateThree:
if(i == 2) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
case MagTrackStateOneAndTwo:
if((i == 0) | (i == 1)) cat_trackstr(tmp_str, cat_count++, i, trackstr);
break;
}
}
widget_add_text_scroll_element(widget, 0, 15, 128, 49, furi_string_get_cstr(tmp_str));
widget_add_button_element(widget, GuiButtonTypeLeft, "Config", mag_widget_callback, mag);
widget_add_button_element(widget, GuiButtonTypeRight, "Send", mag_widget_callback, mag);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_emulate_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case GuiButtonTypeLeft:
consumed = true;
scene_manager_next_scene(scene_manager, MagSceneEmulateConfig);
break;
case GuiButtonTypeRight:
consumed = true;
notification_message(mag->notifications, &sequence_blink_start_cyan);
mag_spoof(mag);
notification_message(mag->notifications, &sequence_blink_stop);
break;
}
}
return consumed;
}
void mag_scene_emulate_on_exit(void* context) {
Mag* mag = context;
notification_message(mag->notifications, &sequence_blink_stop);
widget_reset(mag->widget);
}
@@ -1,286 +0,0 @@
#include "../mag_i.h"
#define TAG "MagSceneEmulateConfig"
enum MagEmulateConfigIndex {
MagEmulateConfigIndexClock,
MagEmulateConfigIndexTrack,
MagEmulateConfigIndexReverse,
MagEmulateConfigIndexRepeat,
MagEmulateConfigIndexTx,
// MagEmulateConfigIndexInterpacket,
};
#define TX_COUNT 7
const char* const tx_text[TX_COUNT] = {
"RFID",
"GPIO",
"Piezo",
"LF + P",
"NFC",
"434MHz",
"868MHz",
};
const uint32_t tx_value[TX_COUNT] = {
MagTxStateRFID,
MagTxStateGPIO,
MagTxStatePiezo,
MagTxStateLF_P,
MagTxStateNFC,
MagTxCC1101_434,
MagTxCC1101_868,
};
#define TRACK_COUNT 4
const char* const track_text[TRACK_COUNT] = {
"1 + 2",
"1",
"2",
"3",
};
const uint32_t track_value[TRACK_COUNT] = {
MagTrackStateOneAndTwo,
MagTrackStateOne,
MagTrackStateTwo,
MagTrackStateThree,
};
#define REVERSE_COUNT 2
const char* const reverse_text[REVERSE_COUNT] = {
"OFF",
"ON",
};
const uint32_t reverse_value[REVERSE_COUNT] = {
MagReverseStateOff,
MagReverseStateOn,
};
#define CLOCK_COUNT 15
const char* const clock_text[CLOCK_COUNT] = {
"200us",
"220us",
"240us",
"250us",
"260us",
"280us",
"300us",
"325us",
"350us",
"375us",
"400us",
"450us",
"500us",
"600us",
"700us",
};
const uint32_t clock_value[CLOCK_COUNT] = {
200,
220,
240,
250,
260,
280,
300,
325,
350,
375,
400,
450,
500,
600,
700,
};
#define INTERPACKET_COUNT 13
const char* const interpacket_text[INTERPACKET_COUNT] = {
"0us",
"2us",
"4us",
"6us",
"8us",
"10us",
"12us",
"14us",
"16us",
"18us",
"20us",
"25us",
"30us",
};
const uint32_t interpacket_value[INTERPACKET_COUNT] = {
0,
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
25,
30,
};
static void mag_scene_emulate_config_set_tx(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, tx_text[index]);
mag->state.tx = tx_value[index];
};
static void mag_scene_emulate_config_set_track(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
if(mag->state.reverse == MagReverseStateOff) {
variable_item_set_current_value_text(item, track_text[index]);
mag->state.track = track_value[index];
} else if(mag->state.reverse == MagReverseStateOn) {
variable_item_set_current_value_index(
item, value_index_uint32(MagTrackStateOneAndTwo, track_value, TRACK_COUNT));
}
// TODO: Check there is data in selected track?
// Only display track options with data?
};
static void mag_scene_emulate_config_set_reverse(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
if(mag->state.track == MagTrackStateOneAndTwo) {
// only allow reverse track to be set when playing both 1 and 2
variable_item_set_current_value_text(item, reverse_text[index]);
mag->state.reverse = reverse_value[index];
//FURI_LOG_D(TAG, "%s", reverse_text[index]);
//FURI_LOG_D(TAG, "%d", mag->setting->reverse);
} else {
variable_item_set_current_value_index(
item, value_index_uint32(MagReverseStateOff, reverse_value, REVERSE_COUNT));
}
};
static void mag_scene_emulate_config_set_repeat_mode(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, reverse_text[index]);
mag->state.repeat_mode = (bool)index;
}
static void mag_scene_emulate_config_set_clock(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, clock_text[index]);
mag->state.us_clock = clock_value[index];
};
static void mag_scene_emulate_config_set_interpacket(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, interpacket_text[index]);
mag->state.us_interpacket = interpacket_value[index];
};
void mag_scene_emulate_config_on_enter(void* context) {
Mag* mag = context;
VariableItem* item;
uint8_t value_index;
// Clock
item = variable_item_list_add(
mag->variable_item_list, "Clock:", CLOCK_COUNT, mag_scene_emulate_config_set_clock, mag);
value_index = value_index_uint32(mag->state.us_clock, clock_value, CLOCK_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, clock_text[value_index]);
// Track
item = variable_item_list_add(
mag->variable_item_list, "Track:", TRACK_COUNT, mag_scene_emulate_config_set_track, mag);
value_index = value_index_uint32(mag->state.track, track_value, TRACK_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, track_text[value_index]);
// Reverse
//FURI_LOG_D(TAG, "%d", mag->setting->reverse);
item = variable_item_list_add(
mag->variable_item_list,
"Reverse:",
REVERSE_COUNT,
mag_scene_emulate_config_set_reverse,
mag);
value_index = value_index_uint32(mag->state.reverse, reverse_value, REVERSE_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, reverse_text[value_index]);
// Repeated TX
item = variable_item_list_add(
mag->variable_item_list,
"Repeat:",
REVERSE_COUNT,
mag_scene_emulate_config_set_repeat_mode,
mag);
value_index = (uint32_t)mag->state.repeat_mode;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, reverse_text[value_index]);
// TX
#ifdef FW_ORIGIN_Official
if(mag->state.is_debug) {
#endif
item = variable_item_list_add(
mag->variable_item_list, "TX via:", TX_COUNT, mag_scene_emulate_config_set_tx, mag);
value_index = value_index_uint32(mag->state.tx, tx_value, TX_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, tx_text[value_index]);
#ifdef FW_ORIGIN_Official
}
#else
variable_item_set_locked(item, !mag->state.is_debug, "Enable Debug!");
#endif
// Interpacket
/*
item = variable_item_list_add(
mag->variable_item_list,
"Interpacket:",
INTERPACKET_COUNT,
mag_scene_emulate_config_set_interpacket,
mag);
value_index =
value_index_uint32(mag->setting->us_interpacket, interpacket_value, INTERPACKET_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, interpacket_text[value_index]);*/
UNUSED(mag_scene_emulate_config_set_interpacket);
variable_item_list_set_selected_item(
mag->variable_item_list,
scene_manager_get_scene_state(mag->scene_manager, MagSceneEmulateConfig));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewVariableItemList);
}
bool mag_scene_emulate_config_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(mag);
UNUSED(scene_manager);
UNUSED(event);
return consumed;
}
void mag_scene_emulate_config_on_exit(void* context) {
Mag* mag = context;
variable_item_list_set_selected_item(mag->variable_item_list, 0);
variable_item_list_reset(mag->variable_item_list);
}
@@ -1,20 +0,0 @@
#include "../mag_i.h"
void mag_scene_exit_confirm_on_enter(void* context) {
Mag* mag = context;
UNUSED(mag);
}
bool mag_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
UNUSED(mag);
UNUSED(event);
bool consumed = false;
return consumed;
}
void mag_scene_exit_confirm_on_exit(void* context) {
Mag* mag = context;
UNUSED(mag);
}
@@ -1,27 +0,0 @@
#include "../mag_i.h"
#include "../mag_device.h"
void mag_scene_file_select_on_enter(void* context) {
Mag* mag = context;
mag_device_set_loading_callback(mag->mag_dev, mag_show_loading_popup, mag);
if(mag_file_select(mag->mag_dev)) {
MagTrackState auto_track = mag_device_autoselect_track_state(mag->mag_dev);
if(auto_track) {
mag->state.track = auto_track;
}
scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
} else {
scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
}
mag_device_set_loading_callback(mag->mag_dev, NULL, mag);
}
bool mag_scene_file_select_on_event(void* context, SceneManagerEvent event) {
UNUSED(context);
UNUSED(event);
return false;
}
void mag_scene_file_select_on_exit(void* context) {
UNUSED(context);
}
@@ -1,82 +0,0 @@
#include <toolbox/name_generator.h>
#include "../mag_i.h"
void mag_scene_input_name_on_enter(void* context) {
Mag* mag = context;
TextInput* text_input = mag->text_input;
FuriString* folder_path;
folder_path = furi_string_alloc();
//TODO: compatible types / etc
//bool name_is_empty = furi_string_empty(mag->mag_dev->dev_name);
bool name_is_empty = true;
if(name_is_empty) {
furi_string_set(mag->file_path, MAG_APP_FOLDER);
name_generator_make_auto(mag->text_store, MAG_TEXT_STORE_SIZE, "Mag");
furi_string_set(folder_path, MAG_APP_FOLDER);
} else {
// TODO: compatible types etc
//mag_text_store_set(mag, "%s", furi_string_get_cstr(mag->mag_dev->dev_name));
path_extract_dirname(furi_string_get_cstr(mag->file_path), folder_path);
}
text_input_set_header_text(text_input, "Name the card");
text_input_set_result_callback(
text_input,
mag_text_input_callback,
mag,
mag->text_store,
MAG_DEV_NAME_MAX_LEN,
name_is_empty);
FURI_LOG_I("", "%s %s", furi_string_get_cstr(folder_path), mag->text_store);
ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
furi_string_get_cstr(folder_path),
MAG_APP_EXTENSION,
furi_string_get_cstr(mag->file_name));
text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
furi_string_free(folder_path);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewTextInput);
}
bool mag_scene_input_name_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventNext) {
consumed = true;
//if(!furi_string_empty(mag->file_name)) {
// mag_delete_key(mag);
//}
furi_string_set(mag->file_name, mag->text_store);
if(mag_device_save(mag->mag_dev, furi_string_get_cstr(mag->file_name))) {
scene_manager_next_scene(scene_manager, MagSceneSaveSuccess);
} else {
//scene_manager_search_and_switch_to_previous_scene(
// scene_manager, MagSceneReadKeyMenu);
// TODO: Replace with appropriate scene! No read scene prior if adding manually...
}
}
}
return consumed;
}
void mag_scene_input_name_on_exit(void* context) {
Mag* mag = context;
TextInput* text_input = mag->text_input;
void* validator_context = text_input_get_validator_callback_context(text_input);
text_input_set_validator(text_input, NULL, NULL);
validator_is_file_free((ValidatorIsFile*)validator_context);
text_input_reset(text_input);
}
@@ -1,37 +0,0 @@
#include "../mag_i.h"
void mag_scene_input_value_on_enter(void* context) {
Mag* mag = context;
Mag_TextInput* mag_text_input = mag->mag_text_input;
// TODO: retrieve stored/existing data if editing rather than adding anew?
mag_text_store_set(mag, furi_string_get_cstr(mag->mag_dev->dev_data.track[1].str));
mag_text_input_set_header_text(mag_text_input, "Enter track data (WIP)");
mag_text_input_set_result_callback(
mag_text_input, mag_text_input_callback, mag, mag->text_store, MAG_TEXT_STORE_SIZE, true);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewTextMagInput);
}
bool mag_scene_input_value_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == MagEventNext) {
consumed = true;
furi_string_set(mag->mag_dev->dev_data.track[1].str, mag->text_store);
scene_manager_next_scene(scene_manager, MagSceneInputName);
}
}
return consumed;
}
void mag_scene_input_value_on_exit(void* context) {
Mag* mag = context;
UNUSED(mag);
}
@@ -1,186 +0,0 @@
// Creator: Hummus@FlipperGang
#include "../mag_i.h"
#include "../helpers/mag_helpers.h"
#include "mag_scene_read.h"
#define TAG "MagSceneRead"
void uart_callback(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
Mag* mag = context;
if(event == FuriHalSerialRxEventData) {
uint8_t data = furi_hal_serial_async_rx(handle);
furi_stream_buffer_send(mag->uart_rx_stream, &data, 1, 0);
furi_thread_flags_set(furi_thread_get_id(mag->uart_rx_thread), WorkerEvtRxDone);
}
}
static int32_t uart_worker(void* context) {
Mag* mag = context;
mag->uart_rx_stream = furi_stream_buffer_alloc(UART_RX_BUF_SIZE, 1);
mag->uart_text_box_store_strlen = 0;
while(1) {
uint32_t events =
furi_thread_flags_wait(WORKER_ALL_RX_EVENTS, FuriFlagWaitAny, FuriWaitForever);
// furi_check((events & FuriFlagError) == 0);
if(events & WorkerEvtStop) break;
if(events & WorkerEvtRxDone) {
FURI_LOG_D(TAG, "WorkerEvtRxDone");
// notification_message(mag->notifications, &sequence_success);
size_t len = furi_stream_buffer_receive(
mag->uart_rx_stream, mag->uart_rx_buf, UART_RX_BUF_SIZE, 200);
FURI_LOG_D(TAG, "UART RX len: %d", len);
if(len > 0) {
// If text box store gets too big, then truncate it
mag->uart_text_box_store_strlen += len;
if(mag->uart_text_box_store_strlen >= UART_TERMINAL_TEXT_BOX_STORE_SIZE - 1) {
furi_string_right(
mag->uart_text_box_store, mag->uart_text_box_store_strlen / 2);
mag->uart_text_box_store_strlen =
furi_string_size(mag->uart_text_box_store) + len;
}
// Add '\0' to the end of the string, and then add the new data
mag->uart_rx_buf[len] = '\0';
furi_string_cat_printf(mag->uart_text_box_store, "%s", mag->uart_rx_buf);
FURI_LOG_D(TAG, "UART RX buf: %*.s", len, mag->uart_rx_buf);
FURI_LOG_D(
TAG, "UART RX store: %s", furi_string_get_cstr(mag->uart_text_box_store));
}
FURI_LOG_D(TAG, "UARTEventRxData");
view_dispatcher_send_custom_event(mag->view_dispatcher, UARTEventRxData);
}
}
furi_stream_buffer_free(mag->uart_rx_stream);
return 0;
}
void update_widgets(Mag* mag) {
// Clear widget from all elements
widget_reset(mag->widget);
// Titlebar
widget_add_icon_element(mag->widget, 38, -1, &I_mag_file_10px);
widget_add_string_element(mag->widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "READ");
widget_add_icon_element(mag->widget, 81, -1, &I_mag_file_10px);
// Text box
widget_add_text_scroll_element(
mag->widget, 0, 10, 128, 40, furi_string_get_cstr(mag->uart_text_box_store));
// Buttons
widget_add_button_element(mag->widget, GuiButtonTypeLeft, "Clear", mag_widget_callback, mag);
widget_add_button_element(mag->widget, GuiButtonTypeRight, "Parse", mag_widget_callback, mag);
}
void mag_scene_read_on_enter(void* context) {
Mag* mag = context;
FuriString* message = furi_string_alloc();
furi_string_printf(message, "Please swipe a card!\n");
mag->uart_text_box_store = message;
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
update_widgets(mag);
// Initialize UART
mag->serial_handle = furi_hal_serial_control_acquire(FuriHalSerialIdUsart);
furi_check(mag->serial_handle);
furi_hal_serial_init(mag->serial_handle, 9600);
furi_hal_serial_async_rx_start(mag->serial_handle, uart_callback, mag, false);
FURI_LOG_D(TAG, "UART initialized");
mag->uart_rx_thread = furi_thread_alloc();
furi_thread_set_name(mag->uart_rx_thread, "UartRx");
furi_thread_set_stack_size(mag->uart_rx_thread, 1024);
furi_thread_set_context(mag->uart_rx_thread, mag);
furi_thread_set_callback(mag->uart_rx_thread, uart_worker);
furi_thread_start(mag->uart_rx_thread);
FURI_LOG_D(TAG, "UART worker started");
}
bool mag_scene_read_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
FURI_LOG_D(TAG, "Custom event: %ld", event.event);
switch(event.event) {
case GuiButtonTypeLeft: // Clear
consumed = true;
// Clear text box store
furi_string_reset(mag->uart_text_box_store);
mag->uart_text_box_store_strlen = 0;
break;
case GuiButtonTypeRight: // Parse
consumed = true;
FURI_LOG_D(TAG, "Trying to parse");
MagDevice* mag_dev = mag->mag_dev;
bool res = mag_device_parse_card_string(mag_dev, mag->uart_text_box_store);
furi_string_reset(mag->uart_text_box_store);
if(res) {
notification_message(mag->notifications, &sequence_success);
furi_string_printf(
mag->uart_text_box_store,
"Track 1: %.*s\nTrack 2: %.*s\nTrack 3: %.*s",
mag_dev->dev_data.track[0].len,
furi_string_get_cstr(mag_dev->dev_data.track[0].str),
mag_dev->dev_data.track[1].len,
furi_string_get_cstr(mag_dev->dev_data.track[1].str),
mag_dev->dev_data.track[2].len,
furi_string_get_cstr(mag_dev->dev_data.track[2].str));
// Switch to saved menu scene
scene_manager_next_scene(mag->scene_manager, MagSceneSavedMenu);
} else {
furi_string_printf(mag->uart_text_box_store, "Failed to parse! Try again\n");
notification_message(mag->notifications, &sequence_error);
}
break;
}
update_widgets(mag);
}
return consumed;
}
void mag_scene_read_on_exit(void* context) {
Mag* mag = context;
// notification_message(mag->notifications, &sequence_blink_stop);
widget_reset(mag->widget);
// view_dispatcher_remove_view(mag->view_dispatcher, MagViewWidget);
// Stop UART worker
FURI_LOG_D(TAG, "Stopping UART worker");
furi_hal_serial_async_rx_stop(mag->serial_handle);
furi_hal_serial_deinit(mag->serial_handle);
furi_hal_serial_control_release(mag->serial_handle);
furi_thread_flags_set(furi_thread_get_id(mag->uart_rx_thread), WorkerEvtStop);
furi_thread_join(mag->uart_rx_thread);
furi_thread_free(mag->uart_rx_thread);
FURI_LOG_D(TAG, "UART worker stopped");
furi_string_free(mag->uart_text_box_store);
notification_message(mag->notifications, &sequence_blink_stop);
}
@@ -1,20 +0,0 @@
#pragma once
#include <gui/modules/text_box.h>
#define UART_RX_BUF_SIZE (320)
#define UART_TERMINAL_TEXT_BOX_STORE_SIZE (4096)
#define UART_TERMINAL_TEXT_INPUT_STORE_SIZE (512)
#define UART_CH (FuriHalSerialIdUsart)
#define UART_BAUDRATE (9600)
typedef enum {
WorkerEvtStop = (1 << 0),
WorkerEvtRxDone = (1 << 1),
} WorkerEvtFlags;
typedef enum {
UARTEventRxData = 100,
} UARTEvents;
#define WORKER_ALL_RX_EVENTS (WorkerEvtStop | WorkerEvtRxDone)
@@ -1,43 +0,0 @@
#include "../mag_i.h"
void mag_scene_save_success_on_enter(void* context) {
Mag* mag = context;
Popup* popup = mag->popup;
// Clear state of data enter scene
//scene_manager_set_scene_state(mag->scene_manager, LfRfidSceneSaveData, 0);
mag_text_store_clear(mag);
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
popup_set_header(popup, "Saved!", 5, 7, AlignLeft, AlignTop);
popup_set_context(popup, mag);
popup_set_callback(popup, mag_popup_timeout_callback);
popup_set_timeout(popup, 1500);
popup_enable_timeout(popup);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewPopup);
}
bool mag_scene_save_success_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if((event.type == SceneManagerEventTypeBack) ||
((event.type == SceneManagerEventTypeCustom) && (event.event == MagEventPopupClosed))) {
bool result =
scene_manager_search_and_switch_to_previous_scene(mag->scene_manager, MagSceneStart);
if(!result) {
scene_manager_search_and_switch_to_another_scene(
mag->scene_manager, MagSceneFileSelect);
}
consumed = true;
}
return consumed;
}
void mag_scene_save_success_on_exit(void* context) {
Mag* mag = context;
popup_reset(mag->popup);
}
@@ -1,50 +0,0 @@
#include "../mag_i.h"
void mag_scene_saved_info_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
// Use strlcpy instead perhaps, to truncate to screen width, then add ellipses if needed?
furi_string_printf(tmp_str, "%s\r\n", mag->mag_dev->dev_name);
widget_add_icon_element(widget, 1, 1, &I_mag_file_10px);
widget_add_string_element(
widget, 13, 2, AlignLeft, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
for(uint8_t i = 0; i < MAG_DEV_TRACKS; i++) {
FuriString* trackstr = mag->mag_dev->dev_data.track[i].str;
furi_string_cat_printf(
tmp_str,
"Track %d:%s%s%s",
(i + 1),
furi_string_empty(trackstr) ? " " : "\n",
furi_string_empty(trackstr) ? "< empty >" : furi_string_get_cstr(trackstr),
(i + 1 == MAG_DEV_TRACKS) ? "" : "\n\n");
}
widget_add_text_scroll_element(widget, 0, 15, 128, 49, furi_string_get_cstr(tmp_str));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_saved_info_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
UNUSED(event);
UNUSED(scene_manager);
return consumed;
}
void mag_scene_saved_info_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
@@ -1,65 +0,0 @@
#include "../mag_i.h"
enum SubmenuIndex {
SubmenuIndexEmulate,
//SubmenuIndexEdit,
SubmenuIndexDelete,
SubmenuIndexInfo,
};
void mag_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
void mag_scene_saved_menu_on_enter(void* context) {
Mag* mag = context;
Submenu* submenu = mag->submenu;
submenu_add_item(
submenu, "Emulate", SubmenuIndexEmulate, mag_scene_saved_menu_submenu_callback, mag);
//submenu_add_item(
// submenu, "Edit (WIP)", SubmenuIndexEdit, mag_scene_saved_menu_submenu_callback, mag);
submenu_add_item(
submenu, "Delete", SubmenuIndexDelete, mag_scene_saved_menu_submenu_callback, mag);
submenu_add_item(
submenu, "Info", SubmenuIndexInfo, mag_scene_saved_menu_submenu_callback, mag);
submenu_set_selected_item(
mag->submenu, scene_manager_get_scene_state(mag->scene_manager, MagSceneSavedMenu));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewSubmenu);
}
bool mag_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
scene_manager_set_scene_state(mag->scene_manager, MagSceneSavedMenu, event.event);
// TODO: replace with actual next scenes once built
if(event.event == SubmenuIndexEmulate) {
scene_manager_next_scene(mag->scene_manager, MagSceneEmulate);
consumed = true;
//} else if(event.event == SubmenuIndexEdit) {
// scene_manager_next_scene(mag->scene_manager, MagSceneUnderConstruction);
// consumed = true;
} else if(event.event == SubmenuIndexDelete) {
scene_manager_next_scene(mag->scene_manager, MagSceneDeleteConfirm);
consumed = true;
} else if(event.event == SubmenuIndexInfo) {
scene_manager_next_scene(mag->scene_manager, MagSceneSavedInfo);
consumed = true;
}
}
return consumed;
}
void mag_scene_saved_menu_on_exit(void* context) {
Mag* mag = context;
submenu_reset(mag->submenu);
}
@@ -1,267 +0,0 @@
#include "../mag_i.h"
#include "../mag_state.h"
#include "../helpers/mag_helpers.h"
#define TAG "MagSceneEmulateConfig"
enum VarItemListIndex {
VarItemListIndexPinInput,
VarItemListIndexPinOutput,
VarItemListIndexPinEnable,
VarItemListIndexNRepeats,
VarItemListIndexRepeatModeOn,
VarItemListIndexAllowUART,
};
static const char* gpio[] = {
[MagPinA7] = "2 (A7)",
[MagPinA6] = "3 (A6)",
[MagPinA4] = "4 (A4)",
[MagPinB3] = "5 (B3)",
[MagPinB2] = "6 (B2)",
[MagPinC3] = "7 (C3)",
[MagPinC1] = "15 (C1)",
[MagPinC0] = "16 (C0)",
};
const uint8_t GPIO_COUNT = COUNT_OF(gpio);
// static const char* uart_pins[] = {[DapUartTypeUSART1] = "13,14", [DapUartTypeLPUART1] = "15,16"};
// static const char* uart_swap[] = {[DapUartTXRXNormal] = "No", [DapUartTXRXSwap] = "Yes"};
#define N_REPEATS_COUNT 10
const char* const n_repeats_text[N_REPEATS_COUNT] = {
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"20",
};
const uint32_t n_repeats_value[N_REPEATS_COUNT] = {
2,
3,
4,
5,
6,
7,
8,
9,
10,
20,
};
#define OFF_ON_COUNT 2
const char* const off_on_text[OFF_ON_COUNT] = {
"OFF",
"ON",
};
void mag_scene_settings_var_item_list_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
static void mag_scene_settings_set_gpio(VariableItem* item, MagPin* pin_out) {
MagPin pin = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, gpio[pin]);
*pin_out = pin;
}
static void mag_scene_settings_set_gpio_input(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_input);
};
static void mag_scene_settings_set_gpio_output(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_output);
};
static void mag_scene_settings_set_gpio_enable(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_gpio(item, &mag->state.pin_enable);
};
static void mag_scene_settings_set_n_repeats(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, n_repeats_text[index]);
mag->state.n_repeats = n_repeats_value[index];
}
static void mag_scene_settings_set_bool(VariableItem* item, bool* bool_out) {
uint8_t index = variable_item_get_current_value_index(item);
variable_item_set_current_value_text(item, off_on_text[index]);
*bool_out = (bool)index;
}
static void mag_scene_settings_set_repeat_mode(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
mag_scene_settings_set_bool(item, &mag->state.repeat_mode);
}
static void mag_scene_settings_set_allow_uart(VariableItem* item) {
Mag* mag = variable_item_get_context(item);
// rising change when value index is truth-y, and prior value false
bool rising = !mag->state.allow_uart && !!variable_item_get_current_value_index(item);
// trigger dialog only on rising change
if(rising) {
scene_manager_set_scene_state(mag->scene_manager, MagSceneSettings, (uint32_t)item);
view_dispatcher_send_custom_event(mag->view_dispatcher, MagEventConfirmDialog);
}
// set value & text based on current varitem index
mag_scene_settings_set_bool(item, &mag->state.allow_uart);
}
static void mag_pin_variable_item_list_add(
Mag* mag,
const char* label,
MagPin pin,
VariableItemChangeCallback change_callback) {
VariableItem* item =
variable_item_list_add(mag->variable_item_list, label, GPIO_COUNT, change_callback, mag);
variable_item_set_current_value_index(item, pin);
variable_item_set_current_value_text(item, gpio[pin]);
}
static void mag_bool_variable_item_list_add(
Mag* mag,
const char* label,
bool value,
VariableItemChangeCallback change_callback) {
VariableItem* item =
variable_item_list_add(mag->variable_item_list, label, OFF_ON_COUNT, change_callback, mag);
uint32_t value_index = (uint32_t)value;
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, off_on_text[value_index]);
}
void mag_scene_settings_on_enter(void* context) {
Mag* mag = context;
VariableItemList* var_item_list = mag->variable_item_list;
VariableItem* item;
uint32_t value_index;
// reload state in the event temporary changes have been
// made on the emulate config screen
// only changes made in this scene should be saved, and this scene
// should always represent the saved settings, not the transient ones for
// a given emulation.
mag_state_load(&mag->state);
mag_pin_variable_item_list_add(
mag, "Input pin:", mag->state.pin_input, mag_scene_settings_set_gpio_input);
mag_pin_variable_item_list_add(
mag, "Output pin:", mag->state.pin_output, mag_scene_settings_set_gpio_output);
mag_pin_variable_item_list_add(
mag, "Enable pin:", mag->state.pin_enable, mag_scene_settings_set_gpio_enable);
mag_bool_variable_item_list_add(
mag, "Repeat default:", mag->state.repeat_mode, mag_scene_settings_set_repeat_mode);
item = variable_item_list_add(
var_item_list, "# repeats: ", N_REPEATS_COUNT, mag_scene_settings_set_n_repeats, mag);
value_index = value_index_uint32(mag->state.n_repeats, n_repeats_value, N_REPEATS_COUNT);
variable_item_set_current_value_index(item, value_index);
variable_item_set_current_value_text(item, n_repeats_text[value_index]);
mag_bool_variable_item_list_add(
mag, "UART MSR:", mag->state.allow_uart, mag_scene_settings_set_allow_uart);
variable_item_list_set_enter_callback(
var_item_list, mag_scene_settings_var_item_list_callback, mag);
variable_item_list_set_selected_item(
var_item_list, scene_manager_get_scene_state(mag->scene_manager, MagSceneSettings));
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewVariableItemList);
}
void mag_scene_settings_dialog_invalid_pins(Mag* mag) {
SceneManager* scene_manager = mag->scene_manager;
DialogMessage* message = dialog_message_alloc();
dialog_message_set_header(message, "Invalid Pin Config!", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(message, "Modify", NULL, "Reset");
dialog_message_set_text(
message,
"Pins cannot overlap.\nChange, or reset to defaults.",
64,
32,
AlignCenter,
AlignCenter);
DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), message);
dialog_message_free(message);
furi_record_close(RECORD_DIALOGS);
if(res == DialogMessageButtonRight) {
mag_state_gpio_reset(&mag->state);
scene_manager_previous_scene(scene_manager);
}
}
bool mag_scene_settings_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
switch(event.type) {
case SceneManagerEventTypeBack:
// when attempting to exit, validate pin configuration
// if invalid, prompt
consumed = true;
if(!mag_state_gpio_is_valid(&mag->state)) {
mag_scene_settings_dialog_invalid_pins(mag);
} else {
scene_manager_previous_scene(scene_manager);
}
break;
case SceneManagerEventTypeCustom:
// scene_manager_set_scene_state(mag->scene_manager, MagSceneSettings, event.event);
consumed = true;
if(event.event == MagEventConfirmDialog) {
DialogMessage* msg = dialog_message_alloc();
dialog_message_set_header(msg, "UART MSR", 64, 0, AlignCenter, AlignTop);
dialog_message_set_buttons(msg, "No", NULL, "Yes");
dialog_message_set_text(
msg,
"This option requires a\nUART-compatible mag reader.\nIs it installed?\n",
64,
32,
AlignCenter,
AlignCenter);
DialogMessageButton res = dialog_message_show(furi_record_open(RECORD_DIALOGS), msg);
if(res != DialogMessageButtonRight) {
// if not "Yes", reset to "OFF" (0 / false-y)
VariableItem* item =
(VariableItem*)scene_manager_get_scene_state(scene_manager, MagSceneSettings);
variable_item_set_current_value_index(item, 0);
mag_scene_settings_set_bool(item, &mag->state.allow_uart);
}
dialog_message_free(msg);
furi_record_close(RECORD_DIALOGS);
// clear item from scene state
scene_manager_set_scene_state(scene_manager, MagSceneSettings, 0);
}
break;
default:
break;
}
return consumed;
}
void mag_scene_settings_on_exit(void* context) {
Mag* mag = context;
variable_item_list_reset(mag->variable_item_list);
mag_state_save(&mag->state);
}
@@ -1,86 +0,0 @@
#include "../mag_i.h"
typedef enum {
SubmenuIndexSaved,
SubmenuIndexRead,
//SubmenuIndexAddManually,
SubmenuIndexSettings,
SubmenuIndexAbout,
} SubmenuIndex;
static void mag_scene_start_submenu_callback(void* context, uint32_t index) {
Mag* mag = context;
view_dispatcher_send_custom_event(mag->view_dispatcher, index);
}
void mag_scene_start_on_enter(void* context) {
Mag* mag = context;
Submenu* submenu = mag->submenu;
submenu_add_item(submenu, "Saved", SubmenuIndexSaved, mag_scene_start_submenu_callback, mag);
submenu_add_lockable_item(
submenu,
"Read",
SubmenuIndexRead,
mag_scene_start_submenu_callback,
mag,
(!mag->state.is_debug && !mag->state.allow_uart),
"Enable Debug!");
//submenu_add_item(
// submenu, "Add Manually", SubmenuIndexAddManually, mag_scene_start_submenu_callback, mag);
submenu_add_item(
submenu, "Settings", SubmenuIndexSettings, mag_scene_start_submenu_callback, mag);
submenu_add_item(submenu, "About", SubmenuIndexAbout, mag_scene_start_submenu_callback, mag);
submenu_set_selected_item(
submenu, scene_manager_get_scene_state(mag->scene_manager, MagSceneStart));
// clear key
furi_string_reset(mag->file_name);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewSubmenu);
}
bool mag_scene_start_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case SubmenuIndexSaved:
furi_string_set(mag->file_path, MAG_APP_FOLDER);
scene_manager_next_scene(mag->scene_manager, MagSceneFileSelect);
consumed = true;
break;
case SubmenuIndexRead:
scene_manager_next_scene(mag->scene_manager, MagSceneRead);
consumed = true;
break;
//case SubmenuIndexAddManually:
// scene_manager_next_scene(mag->scene_manager, MagSceneInputValue);
// consumed = true;
// break;
case SubmenuIndexSettings:
scene_manager_next_scene(mag->scene_manager, MagSceneSettings);
consumed = true;
break;
case SubmenuIndexAbout:
scene_manager_next_scene(mag->scene_manager, MagSceneAbout);
consumed = true;
break;
}
scene_manager_set_scene_state(mag->scene_manager, MagSceneStart, event.event);
}
return consumed;
}
void mag_scene_start_on_exit(void* context) {
Mag* mag = context;
submenu_reset(mag->submenu);
}
@@ -1,40 +0,0 @@
#include "../mag_i.h"
void mag_scene_under_construction_on_enter(void* context) {
Mag* mag = context;
Widget* widget = mag->widget;
FuriString* tmp_str;
tmp_str = furi_string_alloc();
widget_add_button_element(widget, GuiButtonTypeLeft, "Back", mag_widget_callback, mag);
furi_string_printf(tmp_str, "Under construction!");
widget_add_string_element(
widget, 64, 4, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp_str));
furi_string_reset(tmp_str);
view_dispatcher_switch_to_view(mag->view_dispatcher, MagViewWidget);
furi_string_free(tmp_str);
}
bool mag_scene_under_construction_on_event(void* context, SceneManagerEvent event) {
Mag* mag = context;
SceneManager* scene_manager = mag->scene_manager;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == GuiButtonTypeLeft) {
consumed = true;
scene_manager_previous_scene(scene_manager);
}
}
return consumed;
}
void mag_scene_under_construction_on_exit(void* context) {
Mag* mag = context;
widget_reset(mag->widget);
}
+1 -1
View File
@@ -3,7 +3,7 @@ import posixpath
# For more details on these options, run 'fbt -h'
FIRMWARE_ORIGIN = "ARF"
FIRMWARE_ORIGIN = "Official"
# Default hardware target
TARGET_HW = 7
+79
View File
@@ -0,0 +1,79 @@
#include <stdint.h>
#include <stddef.h>
#include "subghz_preset_delta.h"
static const uint8_t preset_delta_AM650_to_FM476[] = {
0x10, 0x67,
0x11, 0x83,
0x12, 0x04,
0x13, 0x02,
0x19, 0x16,
0x21, 0x56,
0x22, 0x10,
};
static const uint8_t preset_delta_AM650_to_FM476_pa[8] = { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const uint8_t preset_delta_AM650_to_FM95[] = {
0x10, 0x67,
0x11, 0x83,
0x12, 0x04,
0x13, 0x02,
0x14, 0xF8,
0x15, 0x24,
0x19, 0x16,
0x21, 0x56,
0x22, 0x10,
};
static const uint8_t preset_delta_AM650_to_FM95_pa[8] = { 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const uint8_t preset_delta_FM476_to_AM650[] = {
0x10, 0x17,
0x11, 0x32,
0x12, 0x30,
0x13, 0x00,
0x19, 0x18,
0x21, 0xB6,
0x22, 0x11,
};
static const uint8_t preset_delta_FM476_to_AM650_pa[8] = { 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const uint8_t preset_delta_FM476_to_FM95[] = {
0x14, 0xF8,
0x15, 0x24,
};
static const uint8_t preset_delta_FM95_to_AM650[] = {
0x10, 0x17,
0x11, 0x32,
0x12, 0x30,
0x13, 0x00,
0x14, 0x00,
0x15, 0x47,
0x19, 0x18,
0x21, 0xB6,
0x22, 0x11,
};
static const uint8_t preset_delta_FM95_to_AM650_pa[8] = { 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static const uint8_t preset_delta_FM95_to_FM476[] = {
0x14, 0x00,
0x15, 0x47,
};
const PresetDeltaEntry preset_delta_table[HOP_PRESET_COUNT][HOP_PRESET_COUNT] = {
{
{0},
{ .delta = preset_delta_AM650_to_FM476, .delta_len = 14, .needs_scal = false, .pa_table = preset_delta_AM650_to_FM476_pa },
{ .delta = preset_delta_AM650_to_FM95, .delta_len = 18, .needs_scal = false, .pa_table = preset_delta_AM650_to_FM95_pa },
},
{
{ .delta = preset_delta_FM476_to_AM650, .delta_len = 14, .needs_scal = false, .pa_table = preset_delta_FM476_to_AM650_pa },
{0},
{ .delta = preset_delta_FM476_to_FM95, .delta_len = 4, .needs_scal = false, .pa_table = NULL },
},
{
{ .delta = preset_delta_FM95_to_AM650, .delta_len = 18, .needs_scal = false, .pa_table = preset_delta_FM95_to_AM650_pa },
{ .delta = preset_delta_FM95_to_FM476, .delta_len = 4, .needs_scal = false, .pa_table = NULL },
{0},
},
};
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
typedef struct {
const uint8_t* delta;
size_t delta_len;
bool needs_scal;
const uint8_t* pa_table;
} PresetDeltaEntry;
typedef enum {
HOP_AM650 = 0,
HOP_FM476 = 1,
HOP_FM95 = 2,
HOP_PRESET_COUNT,
} SubghzHopPreset;
extern const PresetDeltaEntry preset_delta_table[HOP_PRESET_COUNT][HOP_PRESET_COUNT];
-796
View File
@@ -1,796 +0,0 @@
#include "honda_static.h"
#define HONDA_STATIC_BIT_COUNT 64
#define HONDA_STATIC_MIN_SYMBOLS 36
#define HONDA_STATIC_SHORT_BASE_US 28
#define HONDA_STATIC_SHORT_SPAN_US 70
#define HONDA_STATIC_LONG_BASE_US 61
#define HONDA_STATIC_LONG_SPAN_US 130
#define HONDA_STATIC_SYNC_TIME_US 700
#define HONDA_STATIC_ELEMENT_TIME_US 63
#define HONDA_STATIC_UPLOAD_CAPACITY 512
#define HONDA_STATIC_SYMBOL_CAPACITY 512
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
static const char* const honda_static_button_names[9] = {
"LOCK",
"UNLOCK",
"UNKNOWN",
"TRUNK",
"REMOTE START",
"UNKNOWN",
"UNKNOWN",
"PANIC",
"LOCK x2",
};
typedef struct {
uint8_t button;
uint8_t _reserved_01[3];
uint32_t serial;
uint32_t counter;
uint8_t checksum;
uint8_t _reserved_0d[3];
} HondaStaticFields;
struct SubGhzProtocolDecoderHondaStatic {
SubGhzProtocolDecoderBase base;
uint32_t _reserved_0c;
SubGhzBlockDecoder decoder;
uint32_t _reserved_20;
SubGhzBlockGeneric generic;
uint16_t packet_bit_count;
uint8_t _reserved_5a;
uint8_t _reserved_5b;
uint8_t symbols[HONDA_STATIC_SYMBOL_CAPACITY];
uint16_t symbols_count;
HondaStaticFields decoded;
uint8_t decoded_valid;
};
struct SubGhzProtocolEncoderHondaStatic {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
HondaStaticFields decoded;
uint8_t tx_button;
uint8_t _reserved_69[3];
};
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t value = 0;
for(size_t i = 0; i < 8; i++) {
value = (value << 8U) | bytes[i];
}
return value;
}
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
for(size_t i = 0; i < 8; i++) {
bytes[7U - i] = (uint8_t)(value & 0xFFU);
value >>= 8U;
}
}
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return (uint8_t)value;
}
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return value;
}
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte_index = bit_index >> 3U;
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
const uint8_t mask = (uint8_t)(1U << shift);
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
if(bit) {
data[byte_index] |= mask;
} else {
data[byte_index] &= (uint8_t)~mask;
}
}
}
static uint8_t honda_static_level_u8(bool level) {
return level ? 1U : 0U;
}
static uint8_t honda_static_sym_u8(uint8_t stored) {
return stored ? 1U : 0U;
}
static uint8_t honda_static_reverse_bits8(uint8_t value) {
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
return value;
}
static bool honda_static_is_valid_button(uint8_t button) {
if(button > 9U) {
return false;
}
return ((0x336U >> button) & 1U) != 0U;
}
static bool honda_static_is_valid_serial(uint32_t serial) {
return (serial != 0U) && (serial != 0x0FFFFFFFU);
}
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
if(button < 2U) {
return 1U;
}
button -= 2U;
if(button <= 3U) {
return honda_static_encoder_button_map[button];
}
return 1U;
}
static const char* honda_static_button_name(uint8_t button) {
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
return honda_static_button_names[button - 1U];
}
return "Unknown";
}
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
const uint8_t canonical[7] = {
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
compact[5],
compact[6],
compact[7],
};
uint8_t checksum = 0U;
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
checksum ^= canonical[i];
}
return checksum;
}
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
uint8_t compact[8];
honda_static_u64_to_bytes_be(key, compact);
memset(fields, 0, sizeof(*fields));
fields->button = compact[0] & 0x0FU;
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
(uint32_t)compact[7];
fields->checksum = honda_static_compact_bytes_checksum(compact);
}
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
uint8_t compact[8];
compact[0] = fields->button & 0x0FU;
compact[1] = (uint8_t)(fields->serial >> 20U);
compact[2] = (uint8_t)(fields->serial >> 12U);
compact[3] = (uint8_t)(fields->serial >> 4U);
compact[4] = (uint8_t)(fields->serial << 4U);
compact[5] = (uint8_t)(fields->counter >> 16U);
compact[6] = (uint8_t)(fields->counter >> 8U);
compact[7] = (uint8_t)fields->counter;
return honda_static_bytes_to_u64_be(compact);
}
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
memset(packet, 0, 8);
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
honda_static_set_bits(packet, 4, 28, fields->serial);
honda_static_set_bits(packet, 32, 24, fields->counter);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= packet[i];
}
honda_static_set_bits(packet, 56, 8, checksum);
}
static bool
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
const uint8_t button = honda_static_get_bits(packet, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
uint8_t checksum_calc = 0U;
for(size_t i = 0; i < 7; i++) {
checksum_calc ^= packet[i];
}
if(checksum != checksum_calc) {
return false;
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
uint8_t reversed[9];
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
reversed[i] = honda_static_reverse_bits8(packet[i]);
}
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= reversed[i];
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool honda_static_manchester_pack_64(
const uint8_t* symbols,
uint16_t count,
uint16_t start_pos,
bool inverted,
uint8_t packet[9],
uint16_t* out_bit_count) {
memset(packet, 0, 9);
uint16_t pos = start_pos;
uint16_t bit_count = 0U;
while((uint16_t)(pos + 1U) < count) {
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
break;
}
const uint8_t a = honda_static_sym_u8(symbols[pos]);
const uint8_t b = honda_static_sym_u8(symbols[pos + 1U]);
if(a == b) {
pos++;
continue;
}
bool bit = false;
if(inverted) {
bit = (a == 0U) && (b == 1U);
} else {
bit = (a == 1U) && (b == 0U);
}
if(bit) {
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
}
bit_count++;
pos += 2U;
}
if(out_bit_count) {
*out_bit_count = bit_count;
}
return bit_count >= HONDA_STATIC_BIT_COUNT;
}
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
const uint16_t count = instance->symbols_count;
const uint8_t* symbols = instance->symbols;
uint16_t index = 1U;
uint16_t transitions = 0U;
while(index < count) {
if(honda_static_sym_u8(symbols[index]) != honda_static_sym_u8(symbols[index - 1U])) {
transitions++;
} else {
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
break;
}
transitions = 0U;
}
index++;
}
if(index >= count) {
return false;
}
while(((uint16_t)(index + 1U) < count) &&
(honda_static_sym_u8(symbols[index]) == honda_static_sym_u8(symbols[index + 1U]))) {
index++;
}
const uint16_t data_start = index;
uint8_t packet[9] = {0};
uint16_t bit_count = 0U;
if(!honda_static_manchester_pack_64(symbols, count, data_start, inverted, packet, &bit_count)) {
return false;
}
if(honda_static_validate_forward_packet(packet, &instance->decoded)) {
instance->decoded_valid = 1U;
return true;
}
if(inverted) {
return false;
}
if(honda_static_validate_reverse_packet(packet, &instance->decoded)) {
instance->decoded_valid = 1U;
return true;
}
return false;
}
static void honda_static_decoder_commit(SubGhzProtocolDecoderHondaStatic* instance) {
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(&instance->decoded);
instance->generic.serial = instance->decoded.serial;
instance->generic.cnt = instance->decoded.counter;
instance->generic.btn = instance->decoded.button;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
uint8_t packet[8];
honda_static_build_packet_bytes(&instance->decoded, packet);
size_t index = 0U;
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
instance->encoder.upload[index++] =
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
}
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
instance->encoder.upload[index++] =
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
instance->encoder.upload[index++] =
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
}
const bool last_bit = (packet[7] & 1U) != 0U;
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
instance->encoder.front = 0U;
instance->encoder.size_upload = index;
}
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
FuriString* tmp = furi_string_alloc();
if(!tmp) return false;
bool ok = false;
do {
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
const char* key_str = furi_string_get_cstr(tmp);
uint64_t key = 0;
size_t hex_pos = 0;
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9')
nibble = c - '0';
else if(c >= 'A' && c <= 'F')
nibble = c - 'A' + 10;
else if(c >= 'a' && c <= 'f')
nibble = c - 'a' + 10;
else
break;
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) break;
*out_key = key;
ok = true;
} while(false);
furi_string_free(tmp);
return ok;
}
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
.alloc = subghz_protocol_decoder_honda_static_alloc,
.free = subghz_protocol_decoder_honda_static_free,
.feed = subghz_protocol_decoder_honda_static_feed,
.reset = subghz_protocol_decoder_honda_static_reset,
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
.serialize = subghz_protocol_decoder_honda_static_serialize,
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
.get_string = subghz_protocol_decoder_honda_static_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
.alloc = subghz_protocol_encoder_honda_static_alloc,
.free = subghz_protocol_encoder_honda_static_free,
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
.stop = subghz_protocol_encoder_honda_static_stop,
.yield = subghz_protocol_encoder_honda_static_yield,
};
const SubGhzProtocol honda_static_protocol = {
.name = HONDA_STATIC_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_honda_static_decoder,
.encoder = &subghz_protocol_honda_static_encoder,
};
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3U;
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void subghz_protocol_encoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0U;
do {
FuriString* pstr = furi_string_alloc();
if(!pstr) break;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
furi_string_free(pstr);
break;
}
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
furi_string_free(pstr);
break;
}
furi_string_free(pstr);
uint64_t key = 0;
if(!honda_static_read_hex_u64(flipper_format, &key)) {
break;
}
honda_static_unpack_compact(key, &instance->decoded);
uint32_t serial = instance->decoded.serial;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
instance->decoded.serial = serial;
}
uint32_t btn_u32 = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
uint8_t b = (uint8_t)btn_u32;
if(honda_static_is_valid_button(b)) {
instance->decoded.button = b;
} else if(b >= 2U && b <= 5U) {
instance->decoded.button = honda_static_encoder_remap_button(b);
}
}
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
instance->decoded.counter = cnt & 0x00FFFFFFU;
}
instance->generic.serial = instance->decoded.serial;
instance->generic.cnt = instance->decoded.counter;
instance->generic.btn = instance->decoded.button;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(&instance->decoded);
uint8_t key_data[8];
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
flipper_format_rewind(flipper_format);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
status = SubGhzProtocolStatusErrorParserKey;
break;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 3U;
}
honda_static_build_upload(instance);
instance->encoder.is_running = true;
status = SubGhzProtocolStatusOk;
} while(false);
return status;
}
void subghz_protocol_encoder_honda_static_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0U;
}
return current;
}
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
free(instance);
}
void subghz_protocol_decoder_honda_static_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->symbols_count = 0U;
instance->decoded_valid = 0U;
}
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint8_t sym = honda_static_level_u8(level);
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
instance->symbols[instance->symbols_count++] = sym;
}
return;
}
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
instance->symbols[instance->symbols_count++] = sym;
instance->symbols[instance->symbols_count++] = sym;
}
return;
}
const uint16_t sc = instance->symbols_count;
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
if(honda_static_parse_symbols(instance, true) ||
honda_static_parse_symbols(instance, false)) {
honda_static_decoder_commit(instance);
}
}
instance->symbols_count = 0U;
}
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint64_t data = instance->generic.data;
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
}
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
if(!instance->decoded_valid && (instance->generic.data != 0ULL)) {
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
instance->decoded_valid = 1U;
}
furi_string_cat_printf(
output,
"%s %ubit\r\n"
"Key:%016llX\r\n",
instance->generic.protocol_name,
instance->packet_bit_count ? instance->packet_bit_count : HONDA_STATIC_BIT_COUNT,
(unsigned long long)instance->generic.data);
furi_string_cat_printf(
output,
"Btn:%s (0x%X)\r\n"
"Ser:%07lX\r\n"
"Cnt:%06lX Chk:%02X\r\n",
honda_static_button_name(instance->decoded.button),
instance->decoded.button,
(unsigned long)instance->decoded.serial,
(unsigned long)instance->decoded.counter,
instance->decoded.checksum);
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_write_uint32(flipper_format, "Serial", &instance->decoded.serial, 1);
uint32_t temp = instance->decoded.button;
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
flipper_format_write_uint32(flipper_format, "Cnt", &instance->decoded.counter, 1);
temp = instance->decoded.checksum;
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
}
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
if(status != SubGhzProtocolStatusOk) {
return status;
}
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
instance->decoded_valid = 1U;
flipper_format_rewind(flipper_format);
uint32_t s = 0, b = 0, c = 0, k = 0;
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
instance->decoded.serial = s;
}
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
instance->decoded.button = (uint8_t)b;
}
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
instance->decoded.counter = c & 0x00FFFFFFU;
}
if(flipper_format_read_uint32(flipper_format, "Checksum", &k, 1)) {
instance->decoded.checksum = (uint8_t)k;
}
instance->generic.serial = instance->decoded.serial;
instance->generic.cnt = instance->decoded.counter;
instance->generic.btn = instance->decoded.button;
return status;
}
-811
View File
@@ -1,811 +0,0 @@
#include "honda_static.h"
#define HONDA_STATIC_BIT_COUNT 64
#define HONDA_STATIC_MIN_SYMBOLS 36
#define HONDA_STATIC_SHORT_BASE_US 28
#define HONDA_STATIC_SHORT_SPAN_US 70
#define HONDA_STATIC_LONG_BASE_US 61
#define HONDA_STATIC_LONG_SPAN_US 130
#define HONDA_STATIC_SYNC_TIME_US 700
#define HONDA_STATIC_ELEMENT_TIME_US 63
#define HONDA_STATIC_UPLOAD_CAPACITY \
(1U + HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT + (2U * HONDA_STATIC_BIT_COUNT) + 1U)
#define HONDA_STATIC_SYMBOL_CAPACITY 512
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
#define HONDA_STATIC_SYMBOL_BYTE_COUNT ((HONDA_STATIC_SYMBOL_CAPACITY + 7U) / 8U)
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
static const char* const honda_static_button_names[9] = {
"Lock",
"Unlock",
"Unknown",
"Trunk",
"Remote Start",
"Unknown",
"Unknown",
"Panic",
"Lock x2",
};
typedef struct {
uint8_t button;
uint8_t _reserved_01[3];
uint32_t serial;
uint32_t counter;
uint8_t checksum;
uint8_t _reserved_0d[3];
} HondaStaticFields;
struct SubGhzProtocolDecoderHondaStatic {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
uint8_t symbols[HONDA_STATIC_SYMBOL_BYTE_COUNT];
uint16_t symbols_count;
};
struct SubGhzProtocolEncoderHondaStatic {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
HondaStaticFields decoded;
uint8_t tx_button;
uint8_t _reserved_69[3];
};
static void honda_static_decoder_commit(
SubGhzProtocolDecoderHondaStatic* instance,
const HondaStaticFields* decoded);
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t value = 0;
for(size_t i = 0; i < 8; i++) {
value = (value << 8U) | bytes[i];
}
return value;
}
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
for(size_t i = 0; i < 8; i++) {
bytes[7U - i] = (uint8_t)(value & 0xFFU);
value >>= 8U;
}
}
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return (uint8_t)value;
}
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return value;
}
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte_index = bit_index >> 3U;
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
const uint8_t mask = (uint8_t)(1U << shift);
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
if(bit) {
data[byte_index] |= mask;
} else {
data[byte_index] &= (uint8_t)~mask;
}
}
}
static uint8_t honda_static_level_u8(bool level) {
return level ? 1U : 0U;
}
static void honda_static_symbol_set(uint8_t* buf, uint16_t index, uint8_t v) {
const uint8_t byte_index = (uint8_t)(index >> 3U);
const uint8_t shift = (uint8_t)(~index) & 0x07U;
const uint8_t mask = (uint8_t)(1U << shift);
if(v) {
buf[byte_index] |= mask;
} else {
buf[byte_index] &= (uint8_t)~mask;
}
}
static uint8_t honda_static_symbol_get(const uint8_t* buf, uint16_t index) {
const uint8_t byte_index = (uint8_t)(index >> 3U);
const uint8_t shift = (uint8_t)(~index) & 0x07U;
return (uint8_t)((buf[byte_index] >> shift) & 1U);
}
static uint8_t honda_static_reverse_bits8(uint8_t value) {
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
return value;
}
static bool honda_static_is_valid_button(uint8_t button) {
if(button > 9U) {
return false;
}
return ((0x336U >> button) & 1U) != 0U;
}
static bool honda_static_is_valid_serial(uint32_t serial) {
return (serial != 0U) && (serial != 0x0FFFFFFFU);
}
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
if(button < 2U) {
return 1U;
}
button -= 2U;
if(button <= 3U) {
return honda_static_encoder_button_map[button];
}
return 1U;
}
static const char* honda_static_button_name(uint8_t button) {
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
return honda_static_button_names[button - 1U];
}
return "Unknown";
}
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
const uint8_t canonical[7] = {
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
compact[5],
compact[6],
compact[7],
};
uint8_t checksum = 0U;
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
checksum ^= canonical[i];
}
return checksum;
}
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
uint8_t compact[8];
honda_static_u64_to_bytes_be(key, compact);
memset(fields, 0, sizeof(*fields));
fields->button = compact[0] & 0x0FU;
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
(uint32_t)compact[7];
fields->checksum = honda_static_compact_bytes_checksum(compact);
}
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
uint8_t compact[8];
compact[0] = fields->button & 0x0FU;
compact[1] = (uint8_t)(fields->serial >> 20U);
compact[2] = (uint8_t)(fields->serial >> 12U);
compact[3] = (uint8_t)(fields->serial >> 4U);
compact[4] = (uint8_t)(fields->serial << 4U);
compact[5] = (uint8_t)(fields->counter >> 16U);
compact[6] = (uint8_t)(fields->counter >> 8U);
compact[7] = (uint8_t)fields->counter;
return honda_static_bytes_to_u64_be(compact);
}
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
memset(packet, 0, 8);
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
honda_static_set_bits(packet, 4, 28, fields->serial);
honda_static_set_bits(packet, 32, 24, fields->counter);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= packet[i];
}
honda_static_set_bits(packet, 56, 8, checksum);
}
static bool
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
const uint8_t button = honda_static_get_bits(packet, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
uint8_t checksum_calc = 0U;
for(size_t i = 0; i < 7; i++) {
checksum_calc ^= packet[i];
}
if(checksum != checksum_calc) {
return false;
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
uint8_t reversed[9];
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
reversed[i] = honda_static_reverse_bits8(packet[i]);
}
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= reversed[i];
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool honda_static_manchester_pack_64(
const uint8_t* symbol_bits,
uint16_t count,
uint16_t start_pos,
bool inverted,
uint8_t packet[9],
uint16_t* out_bit_count) {
memset(packet, 0, 9);
uint16_t pos = start_pos;
uint16_t bit_count = 0U;
while((uint16_t)(pos + 1U) < count) {
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
break;
}
const uint8_t a = honda_static_symbol_get(symbol_bits, pos);
const uint8_t b = honda_static_symbol_get(symbol_bits, pos + 1U);
if(a == b) {
pos++;
continue;
}
bool bit = false;
if(inverted) {
bit = (a == 0U) && (b == 1U);
} else {
bit = (a == 1U) && (b == 0U);
}
if(bit) {
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
}
bit_count++;
pos += 2U;
}
if(out_bit_count) {
*out_bit_count = bit_count;
}
return bit_count >= HONDA_STATIC_BIT_COUNT;
}
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
const uint16_t count = instance->symbols_count;
const uint8_t* symbol_bits = instance->symbols;
HondaStaticFields decoded;
uint16_t index = 1U;
uint16_t transitions = 0U;
while(index < count) {
if(honda_static_symbol_get(symbol_bits, index) !=
honda_static_symbol_get(symbol_bits, index - 1U)) {
transitions++;
} else {
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
break;
}
transitions = 0U;
}
index++;
}
if(index >= count) {
return false;
}
while(((uint16_t)(index + 1U) < count) && (honda_static_symbol_get(symbol_bits, index) ==
honda_static_symbol_get(symbol_bits, index + 1U))) {
index++;
}
const uint16_t data_start = index;
uint8_t packet[9] = {0};
uint16_t bit_count = 0U;
if(!honda_static_manchester_pack_64(
symbol_bits, count, data_start, inverted, packet, &bit_count)) {
return false;
}
if(honda_static_validate_forward_packet(packet, &decoded)) {
honda_static_decoder_commit(instance, &decoded);
return true;
}
if(inverted) {
return false;
}
if(honda_static_validate_reverse_packet(packet, &decoded)) {
honda_static_decoder_commit(instance, &decoded);
return true;
}
return false;
}
static void honda_static_decoder_commit(
SubGhzProtocolDecoderHondaStatic* instance,
const HondaStaticFields* decoded) {
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(decoded);
instance->generic.serial = decoded->serial;
instance->generic.cnt = decoded->counter;
instance->generic.btn = decoded->button;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
uint8_t packet[8];
honda_static_build_packet_bytes(&instance->decoded, packet);
size_t index = 0U;
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
instance->encoder.upload[index++] =
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
}
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
instance->encoder.upload[index++] =
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
instance->encoder.upload[index++] =
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
}
const bool last_bit = (packet[7] & 1U) != 0U;
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
instance->encoder.front = 0U;
instance->encoder.size_upload = index;
}
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
FuriString* tmp = furi_string_alloc();
if(!tmp) return false;
bool ok = false;
do {
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
const char* key_str = furi_string_get_cstr(tmp);
uint64_t key = 0;
size_t hex_pos = 0;
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9')
nibble = c - '0';
else if(c >= 'A' && c <= 'F')
nibble = c - 'A' + 10;
else if(c >= 'a' && c <= 'f')
nibble = c - 'a' + 10;
else
break;
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) break;
*out_key = key;
ok = true;
} while(false);
furi_string_free(tmp);
return ok;
}
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
.alloc = subghz_protocol_decoder_honda_static_alloc,
.free = subghz_protocol_decoder_honda_static_free,
.feed = subghz_protocol_decoder_honda_static_feed,
.reset = subghz_protocol_decoder_honda_static_reset,
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
.serialize = subghz_protocol_decoder_honda_static_serialize,
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
.get_string = subghz_protocol_decoder_honda_static_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
.alloc = subghz_protocol_encoder_honda_static_alloc,
.free = subghz_protocol_encoder_honda_static_free,
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
.stop = subghz_protocol_encoder_honda_static_stop,
.yield = subghz_protocol_encoder_honda_static_yield,
};
const SubGhzProtocol honda_static_protocol = {
.name = HONDA_STATIC_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_honda_static_decoder,
.encoder = &subghz_protocol_honda_static_encoder,
};
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3U;
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void subghz_protocol_encoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0U;
do {
FuriString* pstr = furi_string_alloc();
if(!pstr) break;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
furi_string_free(pstr);
break;
}
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
furi_string_free(pstr);
break;
}
furi_string_free(pstr);
uint64_t key = 0;
if(!honda_static_read_hex_u64(flipper_format, &key)) {
break;
}
honda_static_unpack_compact(key, &instance->decoded);
uint32_t serial = instance->decoded.serial;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
instance->decoded.serial = serial;
}
uint32_t btn_u32 = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
uint8_t b = (uint8_t)btn_u32;
if(honda_static_is_valid_button(b)) {
instance->decoded.button = b;
} else if(b >= 2U && b <= 5U) {
instance->decoded.button = honda_static_encoder_remap_button(b);
}
}
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
instance->decoded.counter = cnt & 0x00FFFFFFU;
}
instance->generic.serial = instance->decoded.serial;
instance->generic.cnt = instance->decoded.counter;
instance->generic.btn = instance->decoded.button;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(&instance->decoded);
uint8_t key_data[8];
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
flipper_format_rewind(flipper_format);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
status = SubGhzProtocolStatusErrorParserKey;
break;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 3U;
}
honda_static_build_upload(instance);
instance->encoder.is_running = true;
status = SubGhzProtocolStatusOk;
} while(false);
return status;
}
void subghz_protocol_encoder_honda_static_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0U;
}
return current;
}
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
free(instance);
}
void subghz_protocol_decoder_honda_static_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->symbols_count = 0U;
}
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint8_t sym = honda_static_level_u8(level);
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
}
return;
}
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
}
return;
}
const uint16_t sc = instance->symbols_count;
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
if(!honda_static_parse_symbols(instance, true)) {
honda_static_parse_symbols(instance, false);
}
}
instance->symbols_count = 0U;
}
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint64_t data = instance->generic.data;
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
}
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
furi_string_printf(
output,
"%s\r\n"
"Key:%016llX\r\n"
"Btn:%s\r\n"
"Ser:%07lX Cnt:%06lX",
instance->generic.protocol_name,
(unsigned long long)instance->generic.data,
honda_static_button_name(decoded.button),
(unsigned long)decoded.serial,
(unsigned long)decoded.counter);
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
uint32_t temp = decoded.serial;
if(!flipper_format_write_uint32(flipper_format, "Serial", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.button;
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.counter;
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.checksum;
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return status;
}
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
if(status != SubGhzProtocolStatusOk) {
return status;
}
flipper_format_rewind(flipper_format);
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
uint32_t s = 0;
uint32_t b = 0;
uint32_t c = 0;
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
decoded.serial = s;
}
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
decoded.button = (uint8_t)b;
}
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
decoded.counter = c & 0x00FFFFFFU;
}
instance->generic.data = honda_static_pack_compact(&decoded);
instance->generic.serial = decoded.serial;
instance->generic.cnt = decoded.counter;
instance->generic.btn = decoded.button;
return status;
}
-36
View File
@@ -1,36 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <flipper_format/flipper_format.h>
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
extern const SubGhzProtocol honda_static_protocol;
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_static_free(void* context);
void subghz_protocol_decoder_honda_static_reset(void* context);
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_static_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_static_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
-37
View File
@@ -1,37 +0,0 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <flipper_format/flipper_format.h>
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
extern const SubGhzProtocol honda_static_protocol;
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_static_free(void* context);
void subghz_protocol_decoder_honda_static_reset(void* context);
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_static_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_static_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
+166 -102
View File
@@ -170,7 +170,7 @@ static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
((uint64_t)b[6] << 8) | (uint64_t)b[7];
instance->generic.data = key_data;
instance->generic.data_count_bit = 68;
instance->decoder.decode_data = key_data;
instance->decoder.decode_count_bit = 68;
@@ -204,8 +204,8 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v3_v4_decoder,
.encoder = &subghz_protocol_kia_v3_v4_encoder,
@@ -411,7 +411,6 @@ SubGhzProtocolStatus
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
@@ -422,20 +421,31 @@ SubGhzProtocolStatus
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos != 16) {
if(hex_pos < 14) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
decrypted_temp = 0;
}
instance->decrypted = decrypted_temp;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
uint8_t b[8];
@@ -447,11 +457,10 @@ SubGhzProtocolStatus
b[5] = (key >> 16) & 0xFF;
b[6] = (key >> 8) & 0xFF;
b[7] = key & 0xFF;
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
} else {
}
instance->generic.serial = instance->serial;
@@ -468,14 +477,10 @@ SubGhzProtocolStatus
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->cnt = (uint16_t)cnt_temp;
} else if(instance->decrypted != 0) {
instance->cnt = instance->decrypted & 0xFFFF;
} else {
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp;
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
instance->cnt = decrypted_temp & 0xFFFF;
} else {
instance->cnt = 0;
}
instance->cnt = 0;
}
flipper_format_rewind(flipper_format);
@@ -488,6 +493,26 @@ SubGhzProtocolStatus
instance->version = 0;
}
flipper_format_rewind(flipper_format);
uint32_t crc_temp;
if(flipper_format_read_uint32(flipper_format, "CRC", &crc_temp, 1)) {
instance->crc = (uint8_t)crc_temp;
} else {
instance->crc = 0;
}
if(instance->encrypted == 0 && instance->decrypted != 0) {
instance->encrypted = keeloq_common_encrypt(instance->decrypted, KIA_MF_KEY);
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
if(instance->btn == 0) {
instance->btn = instance->generic.btn;
}
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
@@ -505,7 +530,7 @@ SubGhzProtocolStatus
} else {
selected_btn = subghz_custom_btn_get();
}
if(selected_btn == 5) {
instance->btn = 0x8;
} else if(selected_btn >= 1 && selected_btn <= 4) {
@@ -528,48 +553,35 @@ SubGhzProtocolStatus
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t cnt_to_write = instance->cnt;
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t btn_to_write = instance->btn;
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
}
flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t));
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t encrypted_to_write = instance->encrypted;
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t version_to_write = instance->version;
flipper_format_update_uint32(flipper_format, "Version", &version_to_write, 1);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t crc_to_write = instance->crc;
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
}
flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1);
instance->encoder.is_running = true;
instance->encoder.front = 0;
@@ -587,7 +599,6 @@ void subghz_protocol_encoder_kia_v3_v4_stop(void* context) {
instance->encoder.front = 0;
}
static void subghz_protocol_encoder_kia_v3_v4_patch_crc(SubGhzProtocolEncoderKiaV3V4* instance) {
if(!instance || !instance->encoder.upload) return;
const bool v4 = (instance->version == 0);
@@ -655,6 +666,7 @@ void* subghz_protocol_decoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
instance->is_v3_sync = false;
return instance;
}
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
@@ -677,7 +689,7 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
SubGhzProtocolDecoderKiaV3V4* instance = context;
switch(instance->decoder.parser_step) {
case KiaV3V4DecoderStepReset:
case KiaV3V4DecoderStepReset:
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
@@ -778,16 +790,19 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Encrypted", &instance->encrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Decrypted", &instance->decrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -813,71 +828,123 @@ SubGhzProtocolStatus
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize_check_count_bit(&instance->generic, flipper_format, 64);
if(ret == SubGhzProtocolStatusOk) {
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
flipper_format_rewind(flipper_format);
uint32_t bit_count = 0;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count, 1)) {
FURI_LOG_E(TAG, "Missing Bit field");
break;
}
if(bit_count != 68 && bit_count != 64) {
FURI_LOG_E(TAG, "Wrong bit count: %lu", bit_count);
break;
}
instance->generic.data_count_bit = 68;
flipper_format_rewind(flipper_format);
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key field");
furi_string_free(temp_str);
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
} else if(c >= 'A' && c <= 'F') {
nibble = c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos < 14) {
FURI_LOG_E(TAG, "Invalid key: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
instance->decrypted = 0;
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
decrypted_temp = 0;
}
instance->decrypted = decrypted_temp;
flipper_format_rewind(flipper_format);
uint32_t temp_version = 0;
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
instance->version = temp_version;
instance->version = (uint8_t)temp_version;
} else {
instance->version = 0;
}
flipper_format_rewind(flipper_format);
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
instance->crc = (uint8_t)temp_crc;
} else {
instance->crc = 0;
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
}
if(instance->generic.data != 0) {
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
b[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
instance->generic.serial =
((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
}
if(instance->encrypted == 0 && instance->decrypted != 0) {
instance->encrypted = keeloq_common_encrypt(instance->decrypted, KIA_MF_KEY);
}
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(5);
}
return ret;
}
static uint64_t compute_yek(uint64_t key) {
uint64_t yek = 0;
for(int i = 0; i < 64; i++) {
yek |= ((key >> i) & 1) << (63 - i);
}
return yek;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
uint8_t bytes[8];
for(int i = 0; i < 8; i++) {
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
bytes[i] = (data >> ((7 - i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
return (calculated_crc == received_crc);
@@ -887,33 +954,30 @@ void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* out
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
uint64_t yek = compute_yek(instance->generic.data);
uint32_t key_hi = (uint32_t)(instance->generic.data >> 32);
uint32_t key_lo = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
uint32_t yek_hi = (uint32_t)(yek >> 32);
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
uint8_t kb[8];
for(int i = 0; i < 8; i++) {
kb[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Sn:%07lX Btn:%X [%s]\r\n"
"Dec:%08lX Cnt:%04lX\r\n"
"CRC:%X %s",
"Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n"
"Encrypted:%lu\r\n"
"Decrypted:%lu\r\n"
"Btn:%X [%s]\r\n"
"Version:%u\r\n"
"CRC:%u %s",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
kb[0], kb[1], kb[2], kb[3], kb[4], kb[5], kb[6], kb[7],
(unsigned long)instance->encrypted,
(unsigned long)instance->decrypted,
(unsigned)instance->generic.btn,
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
instance->decrypted,
instance->generic.cnt,
instance->crc,
(unsigned)instance->version,
(unsigned)instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
}
+36 -45
View File
@@ -226,8 +226,6 @@ void* subghz_protocol_encoder_kia_v5_alloc(SubGhzEnvironment* environment) {
return instance;
}
static bool subghz_protocol_encoder_kia_v5_get_upload(SubGhzProtocolEncoderKiaV5* instance) {
furi_assert(instance);
@@ -320,7 +318,7 @@ SubGhzProtocolStatus
instance->generic.cnt = mixer_decode(encrypted);
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
instance->generic.cnt = (instance->generic.cnt + mult) & 0xFFFF;
instance->generic.cnt = (instance->generic.cnt + mult) & 0xFFFF;
FURI_LOG_I(TAG, "deserialize #%lu, cnt after=%04lX", call_count, (uint32_t)instance->generic.cnt);
if(subghz_custom_btn_get_original() == 0) {
@@ -560,7 +558,8 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
} else {
if(instance->bit_count >= subghz_protocol_kia_v5_const.min_count_bit_for_found) {
instance->generic.data = instance->saved_key;
instance->generic.data_count_bit = (instance->bit_count > 67) ? 67 : instance->bit_count;
instance->generic.data_count_bit =
(instance->bit_count > 67) ? 67 : instance->bit_count;
instance->crc = (uint8_t)(instance->decoded_data & 0x07);
@@ -599,7 +598,8 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
bool data_bit;
if(instance->bit_count <= 66 &&
manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
kia_v5_add_bit(instance, data_bit);
if(instance->bit_count == 64) {
instance->saved_key = instance->decoded_data;
@@ -627,10 +627,12 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
furi_assert(context);
SubGhzProtocolDecoderKiaV5* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Serial", &instance->generic.serial, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -641,7 +643,8 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1)) {
if(!flipper_format_write_uint32(
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
@@ -651,21 +654,9 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t raw_high = (uint32_t)(instance->generic.data >> 32);
uint32_t raw_low = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
if(!flipper_format_write_uint32(flipper_format, "DataHi", &raw_high, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "DataLo", &raw_low, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t yek_high = (uint32_t)(instance->yek >> 32);
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
if(!flipper_format_write_uint32(flipper_format, "YekHi", &yek_high, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
@@ -690,11 +681,15 @@ SubGhzProtocolStatus
subghz_protocol_kia_v5_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
instance->crc = (uint8_t)temp_crc;
} else {
instance->crc = 0;
}
flipper_format_rewind(flipper_format);
uint32_t yek_high = 0, yek_low = 0;
if(flipper_format_read_uint32(flipper_format, "YekHi", &yek_high, 1) &&
flipper_format_read_uint32(flipper_format, "YekLo", &yek_low, 1)) {
@@ -714,7 +709,7 @@ SubGhzProtocolStatus
}
instance->generic.serial = (uint32_t)((instance->yek >> 32) & 0x0FFFFFFF);
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
uint32_t encrypted = (uint32_t)(instance->yek & 0xFFFFFFFF);
instance->generic.cnt = mixer_decode(encrypted);
@@ -730,11 +725,11 @@ SubGhzProtocolStatus
static const char* subghz_protocol_kia_v5_get_name_button(uint8_t btn) {
switch(btn) {
case 0x01: return "Unlock";
case 0x02: return "Lock";
case 0x04: return "Trunk";
case 0x08: return "Horn";
default: return "Unknown";
case 0x01: return "Unlock";
case 0x02: return "Lock";
case 0x04: return "Trunk";
case 0x08: return "Horn";
default: return "Unknown";
}
}
@@ -742,10 +737,10 @@ void subghz_protocol_decoder_kia_v5_get_string(void* context, FuriString* output
furi_assert(context);
SubGhzProtocolDecoderKiaV5* instance = context;
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0xFFFFFFFF;
uint32_t yek_hi = (uint32_t)(instance->yek >> 32);
uint32_t yek_lo = (uint32_t)(instance->yek & 0xFFFFFFFF);
uint8_t kb[8];
for(int i = 0; i < 8; i++) {
kb[i] = (instance->generic.data >> ((7 - i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v5_calculate_crc(instance->yek);
bool crc_valid = (instance->crc == calculated_crc);
@@ -756,22 +751,18 @@ void subghz_protocol_decoder_kia_v5_get_string(void* context, FuriString* output
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%02X:[%s] Seed:%04X\r\n"
"CRC:%X %s",
"Btn:%02X [%s] Seed:%04X\r\n"
"CRC:%u %s",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn,
kb[0], kb[1], kb[2], kb[3], kb[4], kb[5], kb[6], kb[7],
(unsigned long)instance->generic.serial,
(unsigned long)instance->generic.cnt,
(unsigned)instance->generic.btn,
subghz_protocol_kia_v5_get_name_button(instance->generic.btn),
seed,
instance->crc,
(unsigned)seed,
(unsigned)instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
}
+1 -1
View File
@@ -85,7 +85,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&ford_protocol_v2,
&ford_protocol_v3,
&subghz_protocol_land_rover_v0,
//&subghz_protocol_toyota,
&subghz_protocol_toyota,
};
+3 -2
View File
@@ -79,11 +79,12 @@
#include "scher_khan.h"
#include "sheriff_cfm.h"
#include "chrysler.h"
#include "honda_static.h"
//#include "honda_static.h"
//#include "honda_v1.h"
#include "mazda_v0.h"
#include "kia_v7.h"
#include "ford_v1.h"
#include "ford_v2.h"
#include "ford_v3.h"
#include "land_rover_v0.h"
//#include "toyota.h"
#include "toyota.h"
+642
View File
@@ -0,0 +1,642 @@
#include "toyota.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "SubGhzProtocolToyota"
/*
* TOYOTA KEELOQ — DUAL VARIANT DECODER
*
* VARIANT A — Corolla Verso 2004-2010 (433 MHz)
* TE short : 400 us TE long : 800 us delta: 175 us
* Encoding : PWM pairs — LS=0 (Long HIGH + Short LOW)
* SL=1 (Short HIGH + Long LOW)
* Preamble : repeated SS pairs, ends on first non-SS pair
* Frame : 68 bits
* Repeats : 8x
*
* VARIANT B — Tundra 2011 (315 MHz)
* Encoding : NRZ — each individual pulse encodes one bit:
* pulse <= 287 us -> bit 0
* pulse > 287 us -> bit 1
* Preamble : alternating short/long pulses (~200/~390 us each)
* ends with sync gap ~1938 us (a LOW > 1500 us)
* Frame : 67 bits (each pulse = 1 bit, HIGH and LOW alike)
* Repeats : 30x
* Inter-frame gap: ~51000 us
*
* Confirmed from real capture analysis:
* Preamble HIGHs: ~200 us (short)
* Preamble LOWs : ~390 us (long) — counted as preamble pairs
* Sync gap : ~1938 us LOW after last preamble HIGH
* Data pulses : each pulse independently = 0 if <=287us, 1 if >287us
* Boundary 287 = midpoint between 200 us and 375 us centers
*
* generic.data (64 bits):
* [63..32] = hop (32 bits)
* [31..4] = serial (28 bits)
* [3..0] = button (4 bits)
*
* generic.cnt:
* 0 = Variant A (Corolla/433MHz)
* 1 = Variant B (Tundra/315MHz)
*/
/* ----------------------------------------------------------------
* Physical constants — Variant A
* ---------------------------------------------------------------- */
static const SubGhzBlockConst toyota_const_a = {
.te_short = 400,
.te_long = 800,
.te_delta = 175,
.min_count_bit_for_found = 60,
};
/* ----------------------------------------------------------------
* Physical constants — Variant B (preamble classification only)
* Data phase uses midpoint, not these tolerances.
* ---------------------------------------------------------------- */
static const SubGhzBlockConst toyota_const_b = {
.te_short = 200,
.te_long = 390,
.te_delta = 120,
.min_count_bit_for_found = 60,
};
/*
* NRZ midpoint for Variant B data pulses.
* Pulses <= this value are bit 0, pulses > this value are bit 1.
* Midpoint between short center (200us) and long center (375us).
* Value 287 confirmed correct against real capture:
* 192us->0 181us->0 383us->1 434us->1 380us->1 etc.
*/
#define TOYOTA_B_NRZ_MIDPOINT 287u
/* Sync gap: LOW pulse separating preamble from data */
#define TOYOTA_B_SYNC_GAP_MIN 1500u
#define TOYOTA_B_SYNC_GAP_MAX 2600u
/* Any pulse above this is an inter-frame gap */
#define TOYOTA_INTER_FRAME_GAP 5000u
/* Minimum preamble pairs before accepting sync gap */
#define TOYOTA_B_PREAMBLE_MIN 6u
#define TOYOTA_A_PREAMBLE_MIN 6u
/* Frame lengths in bits */
#define TOYOTA_A_BITS 68u
#define TOYOTA_B_BITS 67u
/* First HIGH duration below this -> Variant B, above -> Variant A */
#define TOYOTA_VARIANT_THRESH 310u
/* ----------------------------------------------------------------
* Button codes
* ---------------------------------------------------------------- */
#define TOYOTA_A_BTN_LOCK 0x08
#define TOYOTA_A_BTN_UNLOCK 0x01
#define TOYOTA_B_BTN_LOCK 0x0A
#define TOYOTA_B_BTN_UNLOCK 0x05
/* ----------------------------------------------------------------
* Parser states
* ---------------------------------------------------------------- */
typedef enum {
ToyotaStepReset = 0,
ToyotaStepPreambleA,
ToyotaStepDataA,
ToyotaStepPreambleB,
ToyotaStepDataB,
} ToyotaDecoderStep;
/* ----------------------------------------------------------------
* Decoder instance
* ---------------------------------------------------------------- */
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t bits_lo;
uint8_t bits_hi;
uint8_t bit_count;
uint32_t te_last;
bool have_high;
uint16_t preamble_count;
uint8_t variant; /* 0 = Corolla/433MHz, 1 = Tundra/315MHz */
uint32_t hop;
uint32_t serial;
uint8_t button;
} SubGhzProtocolDecoderToyota;
/* ----------------------------------------------------------------
* Protocol descriptor
* ---------------------------------------------------------------- */
const SubGhzProtocolDecoder subghz_protocol_toyota_decoder = {
.alloc = subghz_protocol_decoder_toyota_alloc,
.free = subghz_protocol_decoder_toyota_free,
.feed = subghz_protocol_decoder_toyota_feed,
.reset = subghz_protocol_decoder_toyota_reset,
.get_hash_data = subghz_protocol_decoder_toyota_get_hash_data,
.serialize = subghz_protocol_decoder_toyota_serialize,
.deserialize = subghz_protocol_decoder_toyota_deserialize,
.get_string = subghz_protocol_decoder_toyota_get_string,
};
const SubGhzProtocol subghz_protocol_toyota = {
.name = SUBGHZ_PROTOCOL_TOYOTA_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_315 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &subghz_protocol_toyota_decoder,
.encoder = NULL,
};
/* ----------------------------------------------------------------
* TE helpers (preamble phase only)
* ---------------------------------------------------------------- */
static inline bool te_is_short(uint32_t d, const SubGhzBlockConst* c) {
return DURATION_DIFF(d, (uint32_t)c->te_short) < (uint32_t)c->te_delta;
}
static inline bool te_is_long(uint32_t d, const SubGhzBlockConst* c) {
return DURATION_DIFF(d, (uint32_t)c->te_long) < (uint32_t)c->te_delta;
}
/* ----------------------------------------------------------------
* Bit accumulator
* ---------------------------------------------------------------- */
static void toyota_push_bit(SubGhzProtocolDecoderToyota* inst, uint8_t bit) {
uint8_t carry = (uint8_t)(inst->bits_lo >> 63) & 1;
inst->bits_hi = (inst->bits_hi << 1) | carry;
inst->bits_lo = (inst->bits_lo << 1) | (bit & 1);
inst->bit_count++;
}
static uint32_t toyota_extract(
const SubGhzProtocolDecoderToyota* inst,
uint8_t offset,
uint8_t length)
{
uint32_t result = 0;
uint8_t total = inst->bit_count;
for(uint8_t i = 0; i < length; i++) {
int8_t pos = (int8_t)(total - 1) - (int8_t)(offset + i);
uint8_t b = 0;
if(pos >= 64) b = (inst->bits_hi >> (pos - 64)) & 1;
else if(pos >= 0) b = (inst->bits_lo >> pos) & 1;
result = (result << 1) | b;
}
return result;
}
/* ----------------------------------------------------------------
* Name helpers
* ---------------------------------------------------------------- */
static const char* toyota_button_name(uint8_t btn, uint8_t variant) {
if(variant == 1) {
switch(btn & 0x0F) {
case TOYOTA_B_BTN_LOCK: return "Lock";
case TOYOTA_B_BTN_UNLOCK: return "Unlock";
case 0x0F: return "Lock+Unlock";
case 0x04: return "Trunk";
default: return "Unknown";
}
}
switch(btn & 0x0F) {
case TOYOTA_A_BTN_LOCK: return "Lock";
case TOYOTA_A_BTN_UNLOCK: return "Unlock";
case 0x09: return "Lock+Unlock";
case 0x02: return "Trunk";
case 0x04: return "Aux";
default: return "Unknown";
}
}
static const char* toyota_model_name(uint8_t variant) {
return (variant == 1) ? "Tundra" : "Corolla";
}
/* ----------------------------------------------------------------
* Decode and fire callback
* ---------------------------------------------------------------- */
static void toyota_decode_and_fire(SubGhzProtocolDecoderToyota* inst) {
const SubGhzBlockConst* c =
(inst->variant == 1) ? &toyota_const_b : &toyota_const_a;
if(inst->bit_count < (uint8_t)c->min_count_bit_for_found) return;
inst->hop = toyota_extract(inst, 0, 32);
inst->serial = toyota_extract(inst, 32, 28);
inst->button = (uint8_t)toyota_extract(inst, 60, 4);
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 var=%d bits=%d hop=%08lX serial=%07lX btn=%X",
(int)inst->variant, (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);
}
/* ----------------------------------------------------------------
* Alloc / Free / Reset
* ---------------------------------------------------------------- */
void* subghz_protocol_decoder_toyota_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderToyota* inst =
malloc(sizeof(SubGhzProtocolDecoderToyota));
inst->base.protocol = &subghz_protocol_toyota;
inst->generic.protocol_name = inst->base.protocol->name;
return inst;
}
void subghz_protocol_decoder_toyota_free(void* context) {
furi_assert(context);
free(context);
}
void subghz_protocol_decoder_toyota_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
inst->decoder.parser_step = ToyotaStepReset;
inst->decoder.te_last = 0;
inst->bits_lo = 0;
inst->bits_hi = 0;
inst->bit_count = 0;
inst->te_last = 0;
inst->have_high = false;
inst->preamble_count = 0;
inst->hop = 0;
inst->serial = 0;
inst->button = 0;
/* variant intentionally NOT reset — detected once per session */
}
/* ----------------------------------------------------------------
* FEED — Variant A (Corolla 433 MHz)
*
* PWM pair encoding: LS=0, SL=1.
* Preamble: SS pairs until first non-SS pair = first data bit.
* ---------------------------------------------------------------- */
static void toyota_feed_variant_a(
SubGhzProtocolDecoderToyota* inst,
bool level, uint32_t duration)
{
const SubGhzBlockConst* c = &toyota_const_a;
if(inst->decoder.parser_step == ToyotaStepPreambleA) {
if(level) {
inst->te_last = duration;
inst->have_high = true;
return;
}
if(!inst->have_high) {
subghz_protocol_decoder_toyota_reset(inst);
return;
}
inst->have_high = false;
bool hs = te_is_short(inst->te_last, c);
bool hl = te_is_long (inst->te_last, c);
bool ls = te_is_short(duration, c);
bool ll = te_is_long (duration, c);
if(hs && ls) {
inst->preamble_count++;
return;
}
if(inst->preamble_count < TOYOTA_A_PREAMBLE_MIN) {
subghz_protocol_decoder_toyota_reset(inst);
return;
}
inst->bits_lo = 0;
inst->bits_hi = 0;
inst->bit_count = 0;
if (hl && ls) toyota_push_bit(inst, 0);
else if(hs && ll) toyota_push_bit(inst, 1);
inst->decoder.parser_step = ToyotaStepDataA;
return;
}
if(inst->decoder.parser_step == ToyotaStepDataA) {
if(level) {
if(te_is_short(duration, c) || te_is_long(duration, c)) {
inst->te_last = duration;
inst->have_high = true;
} else {
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found)
toyota_decode_and_fire(inst);
subghz_protocol_decoder_toyota_reset(inst);
}
return;
}
if(!inst->have_high) return;
inst->have_high = false;
bool hs = te_is_short(inst->te_last, c);
bool hl = te_is_long (inst->te_last, c);
bool ls = te_is_short(duration, c);
bool ll = te_is_long (duration, c);
if(hl && ls) {
toyota_push_bit(inst, 0);
} else if(hs && ll) {
toyota_push_bit(inst, 1);
} else {
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found)
toyota_decode_and_fire(inst);
subghz_protocol_decoder_toyota_reset(inst);
return;
}
if(inst->bit_count >= TOYOTA_A_BITS) {
toyota_decode_and_fire(inst);
subghz_protocol_decoder_toyota_reset(inst);
}
}
}
/* ----------------------------------------------------------------
* FEED — Variant B (Tundra 315 MHz)
*
* PREAMBLE state:
* Processes HIGH+LOW pairs using tight TE matching.
* Each [SHORT HIGH + LONG LOW] pair increments preamble_count.
* A LOW >= TOYOTA_B_SYNC_GAP_MIN after a valid preamble
* transitions to DATA state.
*
* DATA state — TRUE NRZ:
* Every single pulse (HIGH or LOW) independently encodes one bit.
* The Flipper delivers them alternating level=true/false.
* We process EACH pulse regardless of polarity:
* duration <= TOYOTA_B_NRZ_MIDPOINT (287us) -> bit 0
* duration > TOYOTA_B_NRZ_MIDPOINT -> bit 1
* A pulse >= TOYOTA_B_SYNC_GAP_MIN ends the frame.
* ---------------------------------------------------------------- */
static void toyota_feed_variant_b(
SubGhzProtocolDecoderToyota* inst,
bool level, uint32_t duration)
{
const SubGhzBlockConst* c = &toyota_const_b;
/* ── PREAMBLE ── */
if(inst->decoder.parser_step == ToyotaStepPreambleB) {
if(level) {
if(te_is_short(duration, c)) {
inst->te_last = duration;
inst->have_high = true;
} else {
subghz_protocol_decoder_toyota_reset(inst);
}
return;
}
/* Falling edge */
if(!inst->have_high) {
subghz_protocol_decoder_toyota_reset(inst);
return;
}
inst->have_high = false;
/* Sync gap: LOW ~1938us -> transition to data */
if(duration >= TOYOTA_B_SYNC_GAP_MIN &&
duration <= TOYOTA_B_SYNC_GAP_MAX)
{
if(inst->preamble_count >= TOYOTA_B_PREAMBLE_MIN) {
FURI_LOG_D(TAG, "B: sync gap after %d pairs -> NRZ data",
(int)inst->preamble_count);
inst->bits_lo = 0;
inst->bits_hi = 0;
inst->bit_count = 0;
inst->have_high = false;
inst->decoder.parser_step = ToyotaStepDataB;
} else {
subghz_protocol_decoder_toyota_reset(inst);
}
return;
}
/* Normal preamble LOW must be LONG */
if(te_is_long(duration, c)) {
inst->preamble_count++;
return;
}
subghz_protocol_decoder_toyota_reset(inst);
return;
}
/* ── DATA (NRZ) ── */
if(inst->decoder.parser_step == ToyotaStepDataB) {
/*
* Every pulse — HIGH or LOW — encodes one bit independently.
* A pulse >= sync gap minimum signals end of frame.
* A pulse >= inter-frame gap also ends frame.
*/
if(duration >= TOYOTA_B_SYNC_GAP_MIN) {
/* Frame ended by gap */
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found) {
toyota_decode_and_fire(inst);
}
subghz_protocol_decoder_toyota_reset(inst);
return;
}
/*
* NRZ bit: midpoint classification.
* <= 287us -> bit 0
* > 287us -> bit 1
*/
uint8_t bit = (duration > TOYOTA_B_NRZ_MIDPOINT) ? 1 : 0;
toyota_push_bit(inst, bit);
if(inst->bit_count >= TOYOTA_B_BITS) {
toyota_decode_and_fire(inst);
subghz_protocol_decoder_toyota_reset(inst);
}
}
}
/* ----------------------------------------------------------------
* Public feed — dispatcher
* ---------------------------------------------------------------- */
void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
if(inst->decoder.parser_step == ToyotaStepReset) {
if(!level) return;
/*
* Variant detection from first SHORT HIGH pulse:
* < 310us -> Variant B (Tundra 315MHz, TE_short~200us)
* >= 310us -> Variant A (Corolla 433MHz, TE_short~400us)
*/
bool fits_b = te_is_short(duration, &toyota_const_b) &&
(duration < TOYOTA_VARIANT_THRESH);
bool fits_a = te_is_short(duration, &toyota_const_a) &&
(duration >= TOYOTA_VARIANT_THRESH);
if(fits_b) {
inst->variant = 1;
inst->te_last = duration;
inst->have_high = true;
inst->preamble_count = 0;
inst->decoder.parser_step = ToyotaStepPreambleB;
FURI_LOG_D(TAG, "Detected Variant B (Tundra), first HIGH=%lu",
(unsigned long)duration);
} else if(fits_a) {
inst->variant = 0;
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",
(unsigned long)duration);
}
return;
}
if(inst->variant == 1) {
toyota_feed_variant_b(inst, level, duration);
} else {
toyota_feed_variant_a(inst, level, duration);
}
}
/* ----------------------------------------------------------------
* Hash
* ---------------------------------------------------------------- */
uint8_t subghz_protocol_decoder_toyota_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
return subghz_protocol_blocks_get_hash_data(
&inst->decoder,
(inst->decoder.decode_count_bit / 8) + 1);
}
/* ----------------------------------------------------------------
* Serialize
* ---------------------------------------------------------------- */
SubGhzProtocolStatus subghz_protocol_decoder_toyota_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset)
{
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
inst->generic.cnt = inst->variant;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
/* ----------------------------------------------------------------
* Deserialize
* ---------------------------------------------------------------- */
SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize(
void* context,
FlipperFormat* flipper_format)
{
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize_check_count_bit(
&inst->generic,
flipper_format,
toyota_const_b.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
inst->hop = (uint32_t)(inst->generic.data >> 32);
inst->serial = (uint32_t)((inst->generic.data >> 4) & 0x0FFFFFFF);
inst->button = (uint8_t)(inst->generic.data & 0x0F);
inst->generic.serial = inst->serial;
inst->generic.btn = inst->button;
inst->variant = (inst->generic.cnt != 0) ? 1 : 0;
inst->generic.cnt = inst->variant;
}
return ret;
}
/* ----------------------------------------------------------------
* get_string
* ---------------------------------------------------------------- */
void subghz_protocol_decoder_toyota_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderToyota* inst = context;
uint32_t hop = (uint32_t)(inst->generic.data >> 32);
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;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Hop: %08lX\r\n"
"Sn: %07lX\r\n"
"Btn: %X [%s]",
toyota_model_name(var),
inst->generic.data_count_bit,
(unsigned long)hop,
(unsigned long)serial,
button,
toyota_button_name(button, var));
}
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_TOYOTA_NAME "Toyota"
extern const SubGhzProtocol subghz_protocol_toyota;
void* subghz_protocol_decoder_toyota_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_toyota_free(void* context);
void subghz_protocol_decoder_toyota_reset(void* context);
void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_toyota_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_toyota_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize(
void* context,
FlipperFormat* flipper_format);
void subghz_protocol_decoder_toyota_get_string(void* context, FuriString* output);
+1
View File
@@ -56,6 +56,7 @@ class FlipperApplication:
name: Optional[str] = ""
entry_point: Optional[str] = None
flags: List[str] = field(default_factory=lambda: ["Default"])
cflags: List[str] = field(default_factory=list)
cdefines: List[str] = field(default_factory=list)
requires: List[str] = field(default_factory=list)
conflicts: List[str] = field(default_factory=list)
+9 -37
View File
@@ -1,3 +1,4 @@
import re
import os
import subprocess
@@ -79,46 +80,17 @@ def __invoke_git(args, source_dir):
def _proto_ver_generator(target, source, env):
target_file = target[0]
src_dir = source[0].dir.abspath
changelog_file = source[0]
def fetch(unshallow=False):
git_args = ["fetch", "--tags"]
if unshallow:
git_args.append("--unshallow")
with open(str(changelog_file), "rt") as f:
content = f.read()
try:
__invoke_git(git_args, source_dir=src_dir)
except (subprocess.CalledProcessError, EnvironmentError):
# Not great, not terrible
print(fg.boldred("Git: fetch failed"))
def describe():
try:
return __invoke_git(
["describe", "--tags", "--abbrev=0"],
source_dir=src_dir,
)
except (subprocess.CalledProcessError, EnvironmentError):
return None
fetch()
git_describe = describe()
if not git_describe:
fetch(unshallow=True)
git_describe = describe()
if not git_describe:
git_describe = "0.0"
# Strip leading non-numeric characters (e.g. "v" prefix)
import re
version_match = re.match(r"v?(\d+)(?:\.(\d+))?", git_describe)
if version_match:
git_major = version_match.group(1)
git_minor = version_match.group(2) or "0"
else:
match = re.search(r"##\s*\[(\d+)\.(\d+)\]", content)
if not match:
git_major, git_minor = "0", "0"
else:
git_major, git_minor = match.group(1), match.group(2)
version_file_data = (
"#pragma once",
f"#define PROTOBUF_MAJOR_VERSION {git_major}",
+1
View File
@@ -61,6 +61,7 @@ class AppBuilder:
("FAP_VERSION", f'\\"{".".join(map(str, self.app.fap_version))}\\"'),
*self.app.cdefines,
],
CCFLAGS=self.app.cflags,
)
self.app_env.VariantDir(self.app_work_dir, self.app._appdir, duplicate=False)
+3 -2
View File
@@ -1,5 +1,5 @@
entry,status,name,type,params
Version,+,89.1,,
Version,+,89.2,,
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
Header,+,applications/services/applications.h,,
Header,+,applications/services/bt/bt_service/bt.h,,
@@ -1783,6 +1783,7 @@ Function,-,furi_hal_spi_config_init,void,
Function,-,furi_hal_spi_config_init_early,void,
Function,-,furi_hal_spi_dma_init,void,
Function,+,furi_hal_spi_release,void,const FuriHalSpiBusHandle*
Function,+,furi_hal_subghz_apply_preset_delta,void,"const uint8_t*, size_t, _Bool, const uint8_t*"
Function,-,furi_hal_subghz_dump_state,void,
Function,+,furi_hal_subghz_flush_rx,void,
Function,+,furi_hal_subghz_flush_tx,void,
@@ -3462,8 +3463,8 @@ Function,+,subghz_block_generic_global_reset,void,void*
Function,+,subghz_block_generic_serialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*"
Function,+,subghz_custom_btn_get,uint8_t,
Function,-,subghz_custom_btn_get_long,_Bool,
Function,+,subghz_custom_btn_get_original,uint8_t,
Function,+,subghz_custom_btn_get_max_pages,uint8_t,
Function,+,subghz_custom_btn_get_original,uint8_t,
Function,+,subghz_custom_btn_get_page,uint8_t,
Function,+,subghz_custom_btn_has_pages,_Bool,
Function,+,subghz_custom_btn_is_allowed,_Bool,
1 entry status name type params
2 Version + 89.1 89.2
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/applications.h
5 Header + applications/services/bt/bt_service/bt.h
1783 Function - furi_hal_spi_config_init_early void
1784 Function - furi_hal_spi_dma_init void
1785 Function + furi_hal_spi_release void const FuriHalSpiBusHandle*
1786 Function + furi_hal_subghz_apply_preset_delta void const uint8_t*, size_t, _Bool, const uint8_t*
1787 Function - furi_hal_subghz_dump_state void
1788 Function + furi_hal_subghz_flush_rx void
1789 Function + furi_hal_subghz_flush_tx void
3463 Function + subghz_block_generic_serialize SubGhzProtocolStatus SubGhzBlockGeneric*, FlipperFormat*, SubGhzRadioPreset*
3464 Function + subghz_custom_btn_get uint8_t
3465 Function - subghz_custom_btn_get_long _Bool
Function + subghz_custom_btn_get_original uint8_t
3466 Function + subghz_custom_btn_get_max_pages uint8_t
3467 Function + subghz_custom_btn_get_original uint8_t
3468 Function + subghz_custom_btn_get_page uint8_t
3469 Function + subghz_custom_btn_has_pages _Bool
3470 Function + subghz_custom_btn_is_allowed _Bool
+53
View File
@@ -218,6 +218,59 @@ void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data) {
}
}
/**
* @brief Applies only to the registers that change between the current preset
* and the destination preset, without SRES or a full reload.
*
* Requires the chip state to be RX/TX or IDLE before calling;
* the function itself forces IDLE (without SRES) before writing.
*
* @param delta array of {reg, val} pairs (exact length given by delta_len)
* @param delta_len number of bytes in delta (multiple of 2)
* @param needs_scal if true, forces SCAL after writing to the delta
* @param pa_table 8 bytes of new PA table, or NULL if not changing
*/
void furi_hal_subghz_apply_preset_delta(
const uint8_t* delta,
size_t delta_len,
bool needs_scal,
const uint8_t* pa_table) {
furi_check(delta);
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_subghz);
// Ensure IDLE (not SRES!) before touching records
cc1101_switch_to_idle(&furi_hal_spi_bus_handle_subghz);
furi_check(cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000));
// Write only the records that change
for(size_t i = 0; i + 1 < delta_len; i += 2) {
cc1101_write_reg(&furi_hal_spi_bus_handle_subghz, delta[i], delta[i + 1]);
}
if(needs_scal) {
cc1101_calibrate(&furi_hal_spi_bus_handle_subghz);
furi_check(
cc1101_wait_status_state(&furi_hal_spi_bus_handle_subghz, CC1101StateIDLE, 10000));
}
furi_hal_spi_release(&furi_hal_spi_bus_handle_subghz);
if(pa_table) {
furi_hal_subghz_load_patable(pa_table);
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
FURI_LOG_D(
TAG,
"Applied preset delta: %u regs, scal=%d, pa=%d",
(unsigned)(delta_len / 2),
needs_scal,
pa_table != NULL);
}
}
void furi_hal_subghz_load_registers(const uint8_t* data) {
furi_check(data);
+6
View File
@@ -66,6 +66,12 @@ void furi_hal_subghz_dump_state(void);
*/
void furi_hal_subghz_load_custom_preset(const uint8_t* preset_data);
void furi_hal_subghz_apply_preset_delta(
const uint8_t* delta,
size_t delta_len,
bool needs_scal,
const uint8_t* pa_table);
/** Load registers
*
* @param data Registers data