Merge remote-tracking branch 'mntm/dev' into 956/prs
# Conflicts: # lib/subghz/protocols/protocol_items.c # lib/subghz/protocols/protocol_items.h
@@ -1,52 +1,137 @@
|
||||
### Added:
|
||||
- Apps:
|
||||
- NFC: ISO 15693-3 NFC Writer (by @ch4istO)
|
||||
- Games: Checkers (by @H4W9)
|
||||
- GPIO: CAN Commander (by @MatthewKuKanich)
|
||||
- NFC:
|
||||
- ISO 15693-3 NFC Writer (by @ch4istO)
|
||||
- UL-C Bruteforce (by @noproto)
|
||||
- UL-C Relay (by @noproto)
|
||||
- ULCFkey (by @noproto)
|
||||
- Sub-GHz: ProtoPirate (by @RocketGod-git & @xMasterX & @zero-mega et al.)
|
||||
- Tools: Flipper Wedge (by @dangerous-tac0s)
|
||||
- Sub-GHz:
|
||||
- UL: Add support for 42+ Keeloq based systems (with partial Add Manually support) (by @zero-mega, @xMasterX, ARF Team)
|
||||
- UL: Cardin S449 protocol full support (64bit keeloq) (with Add manually, and all button codes) (use FSK12K modulation to read the remote) (by @xMasterX & @zero-mega)
|
||||
- UL: Beninca ARC AES128 protocol full support (128bit dynamic) (with Add manually, and 2 button codes) (by @xMasterX & @zero-mega)
|
||||
- UL: Jarolift protocol full support (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de))
|
||||
- UL: Treadmill37 protocol support (37bit static) (by @xMasterX)
|
||||
- UL: Ditec GOL4 protocol (with programming mode, button switch, add manually) (by @xMasterX & @zero-mega)
|
||||
- UL: KeyFinder protocol (24bit static) (by @xMasterX & @mishamyte)
|
||||
- UL: Nord ICE protocol (33 bits, Static) (by @xMasterX)
|
||||
- UL: Allstar Firefly 318ALD31K protocol (18 bits, Static) (by @jlaughter)
|
||||
- UL: Add CAME TOP44FGN support in CAME TWEE protocol (by @xMasterX)
|
||||
- UL: New modulation FSK with 12KHz deviation (by @xMasterX)
|
||||
- UL: KingGates Stylo 4k Add manually and button switch support and refactoring of encoder (by @xMasterX)
|
||||
- UL: Stilmatic button 9 support (two buttons hold simulation) (mapped on arrow keys) (by @xMasterX)
|
||||
- UL: Stilmatic (R-Tech) 12bit discr. fix & button 9 support (two buttons hold simulation) (mapped on arrow keys) (by @xMasterX)
|
||||
- UL: Sommer last button code 0x6 support (mapped on arrow keys) (by @xMasterX)
|
||||
- UL: V2 Phoenix (Phox) added 2 counter modes support (docs updated) (by @xMasterX)
|
||||
- UL: Add 390MHz and 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) (by @xMasterX)
|
||||
- UL: Add signals button editor and real remote simulation (full signal transmit with just one click) (by @Dmitry422)
|
||||
- UL: KeeLoq add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps) - may bypass counter on some receivers! (by @xMasterX)
|
||||
- UL: TX Power setting (by @LeeroysHub)
|
||||
- UL: Somfy Keytis button switch and Add Manually support (by @xMasterX)
|
||||
- UL: Genius Echo/Bravo add 2 buttons hold simulation (0xB btn code) (by @xMasterX)
|
||||
- UL: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning (by @xMasterX)
|
||||
- UL: Better support for CAME Atomo type remotes (TOPD4REN) (decode + button codes) (by @xMasterX & Roman)
|
||||
- NFC:
|
||||
- Add Mifare Ultralight C Write Support (#524 by @haw8411)
|
||||
- UL: Add ISO15693-3 and SLIX write-back support (by @DoniyorI)
|
||||
- UL: Add new parsers SZPPK, SKPPK and SevPPK, upgrade Plantain parser, fix TwoCities parser (by @mxcdoam)
|
||||
- OFW PR 4343: RFID: Add Indala 224-bit (long format) protocol support (by @kuzaxak)
|
||||
- UL: JS: Add IR capabilities to the JS engine (by @LuisMayo)
|
||||
- FBT: Allow apps to specify custom cflags (by @WillyJL)
|
||||
- UL: Docs: Add [full list of supported SubGHz protocols](https://github.com/Next-Flip/Momentum-Firmware/blob/dev/documentation/SubGHzSupportedSystems.md) and their frequencies/modulations that can be used for reading remotes (by @xMasterX)
|
||||
UL: API: Add `canvas_get_buffer`, `canvas_get_buffer_size` to public API (by @xMasterX)
|
||||
|
||||
### Updated:
|
||||
- Apps:
|
||||
- XERO: MFKey: 4.1 with 40% faster key recovery, improved memory efficiency (by @dchristle), new SEN dictionary for 10x faster recovery (by @noproto)
|
||||
- UL: Update Sub-GHz apps for FM12K modulation (by @xMasterX)
|
||||
- UL: USB/BT Remote: PTT improvements (by @hryamzik)
|
||||
- Ami Tool: Remove copyrighted names, write card fully using low level poller (by @Firefox2100)
|
||||
- CAN Tools: Parity with DBC format, support importing DBC files (by @MatthewKuKanich)
|
||||
- ESP Flasher: Bump Marauder 1.9.1 (by @justcallmekoko), Marauder 1.9.0 support (by @H4W9)
|
||||
- FlipSocial: Autocomplete, keyboard improvements, bugfixes (by @jblanked)
|
||||
- ESP Flasher: Bump Marauder 1.12.1 (by @justcallmekoko) FlipperHTTP 2.1.1 (by @jblanked) Blackmagic 0.1.2-rc (by @hedger), advanced flashing mode (by @H4W9)
|
||||
- ESP32 WiFi Marauder: Marauder 1.12.0 support (by @pfefferle), Marauder 1.10.0 support (by @justcallmekoko), Marauder 1.9.0 support (by @H4W9)
|
||||
- FlipLibrary: Added Fahrenheit, current weather, and wind speed/direction (by @H4W9)
|
||||
- FlipSocial: Autocomplete, keyboard improvements, explore and profile view enhancements, bugfixes (by @jblanked)
|
||||
- FlipWeather: Added Fahrenheit, current weather, and wind speed/direction, custom location option (by @H4W9)
|
||||
- Free Roam: Minimap, code optimization, ux improvements, online mode, option to hide/show player (by @jblanked)
|
||||
- Flipper Blackhat: TUI command (by @o7-machinehum)
|
||||
- Geometry Dash: Major refactor, bugfixes and performance improvements, rename from Geometry Flip (by @gooseprjkt)
|
||||
- GhostESP: Many bugfixes, added Flock Detection, Flock List, and Stop Flock Detection commands (by @jaylikesbunda)
|
||||
- HC-SR04 Distance Sensor: Option to change measure units (by @Tyl3rA)
|
||||
- IconEdit: Save/Send animations, settings tab with canvas scale and cursor guides, bugfixes (by @rdefeo)
|
||||
- NFC Login: Code refactor, bugfixes, renamed from NFC PC Login (by @Play2BReal)
|
||||
- Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse)
|
||||
- Seos Compatible: Seos write support, various improvements (by @aaronjamt)
|
||||
- INA2xx INA Meter: Fixed application freezing when the sensor is not connected (by @cepetr)
|
||||
- KT0803 FM Transmitter: Many bugfixes (by @nixvio64)
|
||||
- Lidar Emulator: Settings menu with external output and 5V power on support, separate thread for timing the infrared signal (by @ANTodorov)
|
||||
- Lidar Emulator: Support external IR boards (by @ANTodorov)
|
||||
- Metroflip: Bugfixes (by @baalpeteor2)
|
||||
- NearbyFiles: GPS Baudrate selection, Ability to add GPS to file (by @xMasterX)
|
||||
- NFC Login: Code refactor, bugfixes, renamed from NFC PC Login, restore BLE profile on app exit (by @Play2BReal)
|
||||
- Passy: Bugfixes, Save advanced files as well (by @bettse)
|
||||
- Picopass: Option to Create credential without a card (by @redteamlife), info screen improvements, allow Emulation of NRMAC dumped cards, Clean MKF option (by @bettse)
|
||||
- Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse), reverse engineered U90 packet, SNMP support, multi-card selection, memory optimization, refactoring (by @cindersocket)
|
||||
- Seos Compatible: Seos write support, various improvements (by @aaronjamt), support switching key sets (by @pcunning), code refactoring, various bugfixes (by @bettse)
|
||||
- Sub-GHz Scheduler: Added new interval times, bugfixes and improvements (by @shalebridge)
|
||||
- Unitemp: Numerous improvements from @MLAB-project fork (by @MLAB-project)
|
||||
- UL: Update Sub-GHz apps for FM12K modulation (by @xMasterX)
|
||||
- Tetris: Various bugfixes (by @Bricktech2000)
|
||||
- Unitemp: Numerous improvements from @MLAB-project fork (by @MLAB-project), Many changes, new sensors and code refactoring in v2.0 (by @quen0n)
|
||||
- Weebo: Cycle between .nfc files in the same directory (by @sethgregory), new figure IDs (by @Brickleberry19), bugfixes and refactoring (by @bettse)
|
||||
- XRemote: Add dolphin xp and crash bugfix (by @teohumeau)
|
||||
- Yatzee: Fix Full House calculation, improve code readability (by @zessx)
|
||||
- Sub-GHz:
|
||||
- Sub-GHz app is now external on SD card, like on OFW (by @WillyJL)
|
||||
- This was done to free some space on flash, which is always running out
|
||||
- It means RAM consumption while using the Sub-GHz app has increased
|
||||
- Usability remains mostly unaffected, our testing showed it can receive up to ~50 signals normally and ~10 signals with qFlipper / Flipper Mobile App connected
|
||||
- You may experience some "out of memory" messages, this is inevitable as RAM eventually fragments and needs to be rearraged, simply resume as normal after the reboot
|
||||
- Add Manually menu transparently refactored into a "separate app" for lower RAM usage, it is still accessed as before from Sub-GHz app (by @WillyJL)
|
||||
- UL: Counter editor refactoring (by @Dmitry422)
|
||||
- UL: Alutech AT-4N & Nice Flor S turbo speedup (by @Dmitry422)
|
||||
- UL: Sommer fm2 in Add manually now uses FM12K modulation (Sommer without fm2 tag uses FM476) (try this if regular option doesn't work for you) (by @xMasterX)
|
||||
- UL: Replaced Cars ignore option with Revers RB2 protocol ignore option (by @xMasterX)
|
||||
- UL: Improve docs on low level code (by @Dmitry422)
|
||||
- UL: KeeLoq display decrypted `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) (by @xMasterX)
|
||||
- UL: BFT KeeLoq try decoding with zero seed too (by @xMasterX)
|
||||
- UL: KeeLoq display BFT programming mode TX (when arrow button is held) (by @xMasterX)
|
||||
- UL: Signal Settings Improvements (by @Dmitry422)
|
||||
- UL: KeeLoq change delta size (by @xMasterX)
|
||||
- Archive: Support opening and pinning ProtoPirate files from Archive (#510 by @LeeroysHub)
|
||||
- Display: Extend lcd contrast range to -10 to +18 (by @ShaTie)
|
||||
- OFW PR 4297: RFID: Make FDX-B readout more descriptive (by @snowsign)
|
||||
- OFW PR 4320: API: Make `view_port_send_to_back()` public (by @loftyinclination)
|
||||
|
||||
### Fixed:
|
||||
- Sub-GHz:
|
||||
- Fix display of memory full (by @WillyJL)
|
||||
- UL: Fixed button mapping for FAAC RC/XT (by @xMasterX)
|
||||
- UL: Possible Sommer timings fix (by @xMasterX)
|
||||
- UL: Various fixes (by @xMasterX)
|
||||
- UL: Various fixes and cleanup (by @xMasterX)
|
||||
- UL: Nice Flor S remove extra uint64 variable (by @xMasterX)
|
||||
- UL: Fix Alutech AT4N false positives (by @xMasterX)
|
||||
- UL: Fix documentation link for HT12A protocol (by @carlogrisetti)
|
||||
- UL: BFT Mitto fix decode bug (seed was not resetting after one successful decode) (by @xMasterX)
|
||||
- UL: KeeLoq fix display of AN-Motors and HCS101 keys (by @xMasterX)
|
||||
- UL: Fix CAME TWEE repeats count for button click (by @xMasterX)
|
||||
- UL: Fix small logic error in KingGatesStylo4k protocol (by @xMasterX & @X-Stuff)
|
||||
- UL: Improve Nice FLO decoding (by @xMasterX & Roman)
|
||||
- UL: Fix duplicate code in RAW protocol (by @xMasterX)
|
||||
- UL: Fix FAAC SLH wrong decode/encode, apply little code cleanup (by @xMasterX)
|
||||
- NFC:
|
||||
- Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL)
|
||||
- Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (#501 by @WillyJL & aaronjamt)
|
||||
- Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL)
|
||||
- UL: Fix LED not blinking at SLIX unlock (by @xMasterX)
|
||||
- UL: Fix "MIR" and other EMV cards crash on Read (by @Dmitry422)
|
||||
- OFW PR 4362: Fix BusFault in Write to Initial Card (by @akrylysov)
|
||||
- OFW PR 4369: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp)
|
||||
- uFBT: Fix .clangd config for IDEs besides VSCode (by @WillyJL)
|
||||
- UL: Settings: Storage settings exit scenes properly if used via favourites (by @xMasterX)
|
||||
- UL: UI: Some small changes (by @xMasterX)
|
||||
- OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind)
|
||||
- OFW PR 4338: USB: Fix USB HID keyboard LED state reporting (by @Caballosanex)
|
||||
|
||||
### Removed:
|
||||
- Sub-GHz:
|
||||
- Removed Starline, ScherKhan and Kia protocols from main Sub-GHz app, they can be decoded with `Apps > Sub-GHz > ProtoPirate` external app
|
||||
- Disabled X10 and Hormann Bisecur protocols due to flash space constraints and very limited usefulness, Momentum now has same protocol list as Unleashed
|
||||
- Disabled X10 and Hormann Bisecur protocols due to flash space constraints and very limited usefulness, Momentum now has the same protocol list as Unleashed
|
||||
- Desktop: Removed winter holidays anims
|
||||
|
||||
@@ -526,6 +526,134 @@ MU_TEST(test_lfrfid_protocol_fdxb_read_simple) {
|
||||
protocol_dict_free(dict);
|
||||
}
|
||||
|
||||
// Indala224: 224-bit PSK2 frame, no known FC/CN descramble,
|
||||
// test data uses the Proxmark3-verified reference (lf indala reader output)
|
||||
#define INDALA224_TEST_DATA_SIZE 28
|
||||
#define INDALA224_TEST_DATA \
|
||||
{0x80, 0x00, 0x00, 0x01, 0xB2, 0x35, 0x23, 0xA6, 0xC2, 0xE3, 0x1E, 0xBA, 0x3C, 0xBE, \
|
||||
0xE4, 0xAF, 0xB3, 0xC6, 0xAD, 0x1F, 0xCF, 0x64, 0x93, 0x93, 0x92, 0x8C, 0x14, 0xE5}
|
||||
#define INDALA224_BITS_PER_FRAME 224
|
||||
#define INDALA224_US_PER_BIT 255
|
||||
#define INDALA224_FRAMES_TO_DECODE 3
|
||||
|
||||
MU_TEST(test_lfrfid_protocol_indala224_roundtrip) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
mu_assert_int_eq(
|
||||
INDALA224_TEST_DATA_SIZE, protocol_dict_get_data_size(dict, LFRFIDProtocolIndala224));
|
||||
mu_assert_string_eq("Indala224", protocol_dict_get_name(dict, LFRFIDProtocolIndala224));
|
||||
mu_assert_string_eq("Motorola", protocol_dict_get_manufacturer(dict, LFRFIDProtocolIndala224));
|
||||
|
||||
const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_TEST_DATA;
|
||||
|
||||
// Encode: extract bit-level polarity from encoder output.
|
||||
// PSK encoder yields carrier-level oscillation (duration=1 per half-cycle).
|
||||
// PulseGlue produces ~16us outputs which are below the decoder's 127us threshold,
|
||||
// so we extract the bit-level polarity directly: sample the encoder's phase state
|
||||
// at the start of each bit period (every 32 yields = 16 carrier cycles).
|
||||
protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE);
|
||||
mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224));
|
||||
|
||||
const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE;
|
||||
bool* bit_levels = malloc(total_bits);
|
||||
mu_check(bit_levels != NULL);
|
||||
|
||||
for(size_t i = 0; i < total_bits; i++) {
|
||||
LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
|
||||
bit_levels[i] = level_duration_get_level(ld);
|
||||
// Skip remaining 31 yields for this bit period
|
||||
for(size_t skip = 0; skip < 31; skip++) {
|
||||
protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
|
||||
}
|
||||
}
|
||||
|
||||
// Decode: convert bit-level polarities to run-length timing pairs
|
||||
// and feed directly to decoder (simulates hardware PSK demodulator output).
|
||||
protocol_dict_decoders_start(dict);
|
||||
ProtocolId protocol = PROTOCOL_NO;
|
||||
|
||||
size_t i = 0;
|
||||
while(i < total_bits) {
|
||||
bool cur = bit_levels[i];
|
||||
size_t run = 1;
|
||||
while(i + run < total_bits && bit_levels[i + run] == cur) {
|
||||
run++;
|
||||
}
|
||||
uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT);
|
||||
|
||||
protocol = protocol_dict_decoders_feed(dict, cur, duration);
|
||||
if(protocol != PROTOCOL_NO) break;
|
||||
|
||||
i += run;
|
||||
}
|
||||
|
||||
free(bit_levels);
|
||||
|
||||
mu_assert_int_eq(LFRFIDProtocolIndala224, protocol);
|
||||
uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0};
|
||||
protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE);
|
||||
mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE);
|
||||
|
||||
protocol_dict_free(dict);
|
||||
}
|
||||
|
||||
// Indala224 phase-alternating test: data with odd number of 1-bits
|
||||
// causes PSK2 carrier phase to invert between consecutive frames.
|
||||
// Decoder must accept inverted preamble at the second frame boundary.
|
||||
#define INDALA224_ALT_TEST_DATA \
|
||||
{0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
|
||||
|
||||
MU_TEST(test_lfrfid_protocol_indala224_alternating_phase) {
|
||||
ProtocolDict* dict = protocol_dict_alloc(lfrfid_protocols, LFRFIDProtocolMax);
|
||||
|
||||
const uint8_t data[INDALA224_TEST_DATA_SIZE] = INDALA224_ALT_TEST_DATA;
|
||||
|
||||
protocol_dict_set_data(dict, LFRFIDProtocolIndala224, data, INDALA224_TEST_DATA_SIZE);
|
||||
mu_check(protocol_dict_encoder_start(dict, LFRFIDProtocolIndala224));
|
||||
|
||||
const size_t total_bits = INDALA224_BITS_PER_FRAME * INDALA224_FRAMES_TO_DECODE;
|
||||
bool* bit_levels = malloc(total_bits);
|
||||
mu_check(bit_levels != NULL);
|
||||
|
||||
for(size_t i = 0; i < total_bits; i++) {
|
||||
LevelDuration ld = protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
|
||||
bit_levels[i] = level_duration_get_level(ld);
|
||||
for(size_t skip = 0; skip < 31; skip++) {
|
||||
protocol_dict_encoder_yield(dict, LFRFIDProtocolIndala224);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify phase alternation: frame 1 and frame 2 start with opposite polarity
|
||||
mu_check(bit_levels[0] != bit_levels[INDALA224_BITS_PER_FRAME]);
|
||||
|
||||
protocol_dict_decoders_start(dict);
|
||||
ProtocolId protocol = PROTOCOL_NO;
|
||||
|
||||
size_t i = 0;
|
||||
while(i < total_bits) {
|
||||
bool cur = bit_levels[i];
|
||||
size_t run = 1;
|
||||
while(i + run < total_bits && bit_levels[i + run] == cur) {
|
||||
run++;
|
||||
}
|
||||
uint32_t duration = (uint32_t)(run * INDALA224_US_PER_BIT);
|
||||
|
||||
protocol = protocol_dict_decoders_feed(dict, cur, duration);
|
||||
if(protocol != PROTOCOL_NO) break;
|
||||
|
||||
i += run;
|
||||
}
|
||||
|
||||
free(bit_levels);
|
||||
|
||||
mu_assert_int_eq(LFRFIDProtocolIndala224, protocol);
|
||||
uint8_t received_data[INDALA224_TEST_DATA_SIZE] = {0};
|
||||
protocol_dict_get_data(dict, protocol, received_data, INDALA224_TEST_DATA_SIZE);
|
||||
mu_assert_mem_eq(data, received_data, INDALA224_TEST_DATA_SIZE);
|
||||
|
||||
protocol_dict_free(dict);
|
||||
}
|
||||
|
||||
MU_TEST_SUITE(test_lfrfid_protocols_suite) {
|
||||
MU_RUN_TEST(test_lfrfid_protocol_em_read_simple);
|
||||
MU_RUN_TEST(test_lfrfid_protocol_em_emulate_simple);
|
||||
@@ -538,6 +666,9 @@ MU_TEST_SUITE(test_lfrfid_protocols_suite) {
|
||||
|
||||
MU_RUN_TEST(test_lfrfid_protocol_inadala26_emulate_simple);
|
||||
|
||||
MU_RUN_TEST(test_lfrfid_protocol_indala224_roundtrip);
|
||||
MU_RUN_TEST(test_lfrfid_protocol_indala224_alternating_phase);
|
||||
|
||||
MU_RUN_TEST(test_lfrfid_protocol_fdxb_read_simple);
|
||||
MU_RUN_TEST(test_lfrfid_protocol_fdxb_emulate_simple);
|
||||
}
|
||||
|
||||
@@ -826,12 +826,17 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
subghz_device_cc1101_ext->async_tx.buffer =
|
||||
malloc(SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t));
|
||||
|
||||
// here we do the same things as in /unleashed-firmware/targets/f7/furi_hal/furi_hal_subghz.c
|
||||
// use first DMA to update timer TIM17 durations, but TIM17 have not output chanel
|
||||
// so we use second DMA to transfer data from gpio_tx_buff directly to gpio pin using BSSR.
|
||||
// BSSR allow us tranfer data directly to pin in gpio port.
|
||||
|
||||
//Signal generation with mem-to-mem DMA
|
||||
furi_hal_gpio_write(subghz_device_cc1101_ext->g0_pin, false);
|
||||
furi_hal_gpio_init(
|
||||
subghz_device_cc1101_ext->g0_pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
|
||||
|
||||
// Configure DMA update timer
|
||||
// Configure DMA to update timer TIM17 ARR by durations from buffer
|
||||
LL_DMA_SetMemoryAddress(
|
||||
SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.buffer);
|
||||
LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t) & (TIM17->ARR));
|
||||
@@ -853,7 +858,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
|
||||
furi_hal_bus_enable(FuriHalBusTIM17);
|
||||
|
||||
// Configure TIM
|
||||
// Configure TIM 17
|
||||
// Set the timer resolution to 2 us
|
||||
LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP);
|
||||
LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1);
|
||||
@@ -867,7 +872,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb
|
||||
subghz_device_cc1101_ext_async_tx_refill(
|
||||
subghz_device_cc1101_ext->async_tx.buffer, SUBGHZ_DEVICE_CC1101_EXT_ASYNC_TX_BUFFER_FULL);
|
||||
|
||||
// Configure tx gpio dma
|
||||
// Configure DMA to transfer data from gpio_tx_buff directly to gpio pin using BSSR
|
||||
const GpioPin* gpio = subghz_device_cc1101_ext->g0_pin;
|
||||
|
||||
subghz_device_cc1101_ext->async_tx.gpio_tx_buff[0] = (uint32_t)gpio->pin << GPIO_NUMBER;
|
||||
|
||||
@@ -306,6 +306,33 @@ App(
|
||||
sources=["plugins/supported_cards/plantain.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="szppk_so_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="szppk_so_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/szppk_so.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="sk_tk_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="sk_tk_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/sk_tk.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="sev_tk_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="sev_tk_plugin_ep",
|
||||
targets=["f7"],
|
||||
requires=["nfc"],
|
||||
sources=["plugins/supported_cards/sevppk_tk.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="two_cities_parser",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
@@ -605,13 +632,15 @@ App(
|
||||
"cli/commands/dump/protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.c",
|
||||
"cli/commands/dump/protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.c",
|
||||
"cli/commands/dump/protocols/iso15693_3/nfc_cli_dump_iso15693_3.c",
|
||||
"cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c",
|
||||
"cli/commands/dump/protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.c",
|
||||
"cli/commands/dump/protocols/mf_classic/nfc_cli_dump_mf_classic.c",
|
||||
"cli/commands/dump/protocols/mf_plus/nfc_cli_dump_mf_plus.c",
|
||||
"cli/commands/dump/protocols/mf_desfire/nfc_cli_dump_mf_desfire.c",
|
||||
"cli/commands/dump/protocols/slix/nfc_cli_dump_slix.c",
|
||||
"cli/commands/dump/protocols/st25tb/nfc_cli_dump_st25tb.c",
|
||||
"cli/commands/dump/protocols/felica/nfc_cli_dump_felica.c",
|
||||
"cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c",
|
||||
"cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c",
|
||||
"cli/commands/mfu/nfc_cli_command_mfu.c",
|
||||
"cli/commands/mfu/nfc_cli_action_info.c",
|
||||
"cli/commands/mfu/nfc_cli_action_rdbl.c",
|
||||
|
||||
@@ -9,13 +9,15 @@
|
||||
#include "protocols/iso14443_4a/nfc_cli_dump_iso14443_4a.h"
|
||||
#include "protocols/iso14443_4b/nfc_cli_dump_iso14443_4b.h"
|
||||
#include "protocols/iso15693_3/nfc_cli_dump_iso15693_3.h"
|
||||
#include "protocols/mf_classic/nfc_cli_dump_mf_classic.h"
|
||||
#include "protocols/mf_desfire/nfc_cli_dump_mf_desfire.h"
|
||||
#include "protocols/mf_plus/nfc_cli_dump_mf_plus.h"
|
||||
#include "protocols/felica/nfc_cli_dump_felica.h"
|
||||
#include "protocols/mf_ultralight/nfc_cli_dump_mf_ultralight.h"
|
||||
#include "protocols/mf_classic/nfc_cli_dump_mf_classic.h"
|
||||
#include "protocols/mf_plus/nfc_cli_dump_mf_plus.h"
|
||||
#include "protocols/mf_desfire/nfc_cli_dump_mf_desfire.h"
|
||||
#include "protocols/slix/nfc_cli_dump_slix.h"
|
||||
#include "protocols/st25tb/nfc_cli_dump_st25tb.h"
|
||||
#include "protocols/felica/nfc_cli_dump_felica.h"
|
||||
#include "protocols/ntag4xx/nfc_cli_dump_ntag4xx.h"
|
||||
#include "protocols/type_4_tag/nfc_cli_dump_type_4_tag.h"
|
||||
|
||||
#include <datetime.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
@@ -81,18 +83,20 @@ static bool nfc_cli_dump_parse_filename_key(FuriString* value, void* output) {
|
||||
}
|
||||
|
||||
NfcGenericCallback protocol_poller_callbacks[NfcProtocolNum] = {
|
||||
[NfcProtocolMfUltralight] = nfc_cli_dump_poller_callback_mf_ultralight,
|
||||
[NfcProtocolMfClassic] = nfc_cli_dump_poller_callback_mf_classic,
|
||||
[NfcProtocolFelica] = nfc_cli_dump_poller_callback_felica,
|
||||
[NfcProtocolIso14443_3a] = nfc_cli_dump_poller_callback_iso14443_3a,
|
||||
[NfcProtocolIso14443_3b] = nfc_cli_dump_poller_callback_iso14443_3b,
|
||||
[NfcProtocolIso14443_4a] = nfc_cli_dump_poller_callback_iso14443_4a,
|
||||
[NfcProtocolIso14443_4b] = nfc_cli_dump_poller_callback_iso14443_4b,
|
||||
[NfcProtocolIso15693_3] = nfc_cli_dump_poller_callback_iso15693_3,
|
||||
[NfcProtocolSlix] = nfc_cli_dump_poller_callback_slix,
|
||||
[NfcProtocolMfDesfire] = nfc_cli_dump_poller_callback_mf_desfire,
|
||||
[NfcProtocolFelica] = nfc_cli_dump_poller_callback_felica,
|
||||
[NfcProtocolMfUltralight] = nfc_cli_dump_poller_callback_mf_ultralight,
|
||||
[NfcProtocolMfClassic] = nfc_cli_dump_poller_callback_mf_classic,
|
||||
[NfcProtocolMfPlus] = nfc_cli_dump_poller_callback_mf_plus,
|
||||
[NfcProtocolMfDesfire] = nfc_cli_dump_poller_callback_mf_desfire,
|
||||
[NfcProtocolSlix] = nfc_cli_dump_poller_callback_slix,
|
||||
[NfcProtocolSt25tb] = nfc_cli_dump_poller_callback_st25tb,
|
||||
[NfcProtocolNtag4xx] = nfc_cli_dump_poller_callback_ntag4xx,
|
||||
[NfcProtocolType4Tag] = nfc_cli_dump_poller_callback_type_4_tag,
|
||||
};
|
||||
|
||||
static void nfc_cli_dump_generate_filename(FuriString* file_path) {
|
||||
@@ -219,6 +223,8 @@ static const NfcProtocolNameValuePair supported_protocols[] = {
|
||||
{.name = "des", .value = NfcProtocolMfDesfire},
|
||||
{.name = "slix", .value = NfcProtocolSlix},
|
||||
{.name = "st25", .value = NfcProtocolSt25tb},
|
||||
{.name = "ntag4", .value = NfcProtocolNtag4xx},
|
||||
{.name = "t4t", .value = NfcProtocolType4Tag},
|
||||
};
|
||||
|
||||
static bool nfc_cli_dump_parse_protocol(FuriString* value, void* output) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "nfc_cli_dump_ntag4xx.h"
|
||||
#include <nfc/protocols/ntag4xx/ntag4xx_poller.h>
|
||||
|
||||
#define TAG "NTAG4XX"
|
||||
|
||||
NfcCommand nfc_cli_dump_poller_callback_ntag4xx(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolNtag4xx);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
NfcCliDumpContext* instance = context;
|
||||
const Ntag4xxPollerEvent* ntag4xx_event = event.event_data;
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadSuccess) {
|
||||
nfc_device_set_data(
|
||||
instance->nfc_device, NfcProtocolNtag4xx, nfc_poller_get_data(instance->poller));
|
||||
instance->result = NfcCliDumpErrorNone;
|
||||
command = NfcCommandStop;
|
||||
} else if(ntag4xx_event->type == Ntag4xxPollerEventTypeReadFailed) {
|
||||
instance->result = NfcCliDumpErrorFailedToRead;
|
||||
command = NfcCommandReset;
|
||||
}
|
||||
|
||||
if(command == NfcCommandStop) {
|
||||
furi_semaphore_release(instance->sem_done);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nfc_cli_dump_common_types.h"
|
||||
|
||||
NfcCommand nfc_cli_dump_poller_callback_ntag4xx(NfcGenericEvent event, void* context);
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "nfc_cli_dump_type_4_tag.h"
|
||||
#include <nfc/protocols/type_4_tag/type_4_tag_poller.h>
|
||||
|
||||
#define TAG "TYPE4TAG"
|
||||
|
||||
NfcCommand nfc_cli_dump_poller_callback_type_4_tag(NfcGenericEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
furi_assert(event.protocol == NfcProtocolType4Tag);
|
||||
furi_assert(event.event_data);
|
||||
|
||||
NfcCliDumpContext* instance = context;
|
||||
const Type4TagPollerEvent* type_4_tag_event = event.event_data;
|
||||
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(type_4_tag_event->type == Type4TagPollerEventTypeReadSuccess) {
|
||||
nfc_device_set_data(
|
||||
instance->nfc_device, NfcProtocolType4Tag, nfc_poller_get_data(instance->poller));
|
||||
instance->result = NfcCliDumpErrorNone;
|
||||
command = NfcCommandStop;
|
||||
} else if(type_4_tag_event->type == Type4TagPollerEventTypeReadFailed) {
|
||||
instance->result = NfcCliDumpErrorFailedToRead;
|
||||
command = NfcCommandReset;
|
||||
}
|
||||
|
||||
if(command == NfcCommandStop) {
|
||||
furi_semaphore_release(instance->sem_done);
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "../nfc_cli_dump_common_types.h"
|
||||
|
||||
NfcCommand nfc_cli_dump_poller_callback_type_4_tag(NfcGenericEvent event, void* context);
|
||||
@@ -9,10 +9,12 @@ static const char* protocol_names[NfcProtocolNum] = {
|
||||
[NfcProtocolFelica] = "FeliCa",
|
||||
[NfcProtocolMfUltralight] = "Mifare Ultralight",
|
||||
[NfcProtocolMfClassic] = "Mifare Classic",
|
||||
[NfcProtocolMfDesfire] = "Mifare DESFire",
|
||||
[NfcProtocolMfPlus] = "Mifare Plus",
|
||||
[NfcProtocolMfDesfire] = "Mifare DESFire",
|
||||
[NfcProtocolSlix] = "Slix",
|
||||
[NfcProtocolSt25tb] = "St25tb",
|
||||
[NfcProtocolNtag4xx] = "Ntag4xx",
|
||||
[NfcProtocolType4Tag] = "Type 4 Tag",
|
||||
};
|
||||
|
||||
const char* nfc_cli_get_protocol_name(NfcProtocol protocol) {
|
||||
|
||||
@@ -117,9 +117,12 @@ const NfcCliRawProtocolSpecificHandler nfc_cli_raw_protocol_handlers[] = {
|
||||
[NfcProtocolFelica] = nfc_cli_raw_felica_handler,
|
||||
[NfcProtocolMfUltralight] = NULL,
|
||||
[NfcProtocolMfClassic] = NULL,
|
||||
[NfcProtocolMfPlus] = NULL,
|
||||
[NfcProtocolMfDesfire] = NULL,
|
||||
[NfcProtocolSlix] = NULL,
|
||||
[NfcProtocolSt25tb] = NULL,
|
||||
[NfcProtocolNtag4xx] = NULL,
|
||||
[NfcProtocolType4Tag] = NULL,
|
||||
};
|
||||
|
||||
static NfcCommand nfc_cli_raw_poller_callback(NfcGenericEventEx event, void* context) {
|
||||
|
||||
@@ -109,9 +109,76 @@ static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
|
||||
instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
|
||||
}
|
||||
|
||||
static NfcCommand
|
||||
nfc_scene_write_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolIso15693_3);
|
||||
|
||||
NfcApp* instance = context;
|
||||
Iso15693_3Poller* poller = event.instance;
|
||||
const Iso15693_3PollerEvent* iso15693_3_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) {
|
||||
const Iso15693_3Data* write_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolIso15693_3);
|
||||
const Iso15693_3Data* card_data = iso15693_3_poller_get_data(poller);
|
||||
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
|
||||
|
||||
if(card_data->system_info.block_count != write_data->system_info.block_count ||
|
||||
card_data->system_info.block_size != write_data->system_info.block_size) {
|
||||
furi_string_set(
|
||||
instance->text_box_store, "Incompatible card\n(block count/size\nmismatch)");
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
} else {
|
||||
Iso15693_3Error error = Iso15693_3ErrorNone;
|
||||
const uint8_t block_size = write_data->system_info.block_size;
|
||||
const uint8_t* write_block_data =
|
||||
(const uint8_t*)simple_array_cget_data(write_data->block_data);
|
||||
const uint8_t* card_block_data =
|
||||
(const uint8_t*)simple_array_cget_data(card_data->block_data);
|
||||
for(uint16_t i = 0; i < write_data->system_info.block_count; i++) {
|
||||
if(iso15693_3_is_block_locked(write_data, i)) continue;
|
||||
// Skip blocks that already contain the correct data
|
||||
if(memcmp(
|
||||
write_block_data + i * block_size,
|
||||
card_block_data + i * block_size,
|
||||
block_size) == 0)
|
||||
continue;
|
||||
error = iso15693_3_poller_write_block(
|
||||
poller, write_block_data + i * block_size, i, block_size);
|
||||
if(error == Iso15693_3ErrorInternal) {
|
||||
// Block is locked on the target card, skip it
|
||||
error = Iso15693_3ErrorNone;
|
||||
continue;
|
||||
}
|
||||
if(error != Iso15693_3ErrorNone) break;
|
||||
}
|
||||
|
||||
if(error == Iso15693_3ErrorNone) {
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||
} else {
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
}
|
||||
}
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static void nfc_scene_write_on_enter_iso15693_3(NfcApp* instance) {
|
||||
furi_string_set(instance->text_box_store, "Apply the\ntarget card now");
|
||||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolIso15693_3);
|
||||
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_iso15693_3, instance);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid |
|
||||
NfcProtocolFeatureMoreInfo,
|
||||
NfcProtocolFeatureMoreInfo | NfcProtocolFeatureWrite,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
@@ -155,7 +222,7 @@ const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
|
||||
},
|
||||
.scene_write =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_enter = nfc_scene_write_on_enter_iso15693_3,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -14,7 +14,9 @@ enum {
|
||||
SubmenuIndexUnlock = SubmenuIndexCommonMax,
|
||||
SubmenuIndexUnlockByReader,
|
||||
SubmenuIndexUnlockByPassword,
|
||||
SubmenuIndexDictAttack
|
||||
SubmenuIndexDictAttack,
|
||||
SubmenuIndexWriteKeepKey, // ULC: write data pages, keep target card's existing key
|
||||
SubmenuIndexWriteCopyKey, // ULC: write all pages including key from source card
|
||||
};
|
||||
|
||||
enum {
|
||||
@@ -214,8 +216,26 @@ static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instanc
|
||||
if(is_locked ||
|
||||
(data->type != MfUltralightTypeNTAG213 && data->type != MfUltralightTypeNTAG215 &&
|
||||
data->type != MfUltralightTypeNTAG216 && data->type != MfUltralightTypeUL11 &&
|
||||
data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin)) {
|
||||
data->type != MfUltralightTypeUL21 && data->type != MfUltralightTypeOrigin &&
|
||||
data->type != MfUltralightTypeMfulC)) {
|
||||
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
|
||||
} else if(data->type == MfUltralightTypeMfulC) {
|
||||
// Replace the generic Write item with two ULC-specific options so the user
|
||||
// can choose whether to keep or overwrite the target card's 3DES key.
|
||||
// This avoids any mid-write dialog/view-switching complexity entirely.
|
||||
submenu_remove_item(submenu, SubmenuIndexCommonWrite);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write (Keep Key)",
|
||||
SubmenuIndexWriteKeepKey,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write (Copy Key)",
|
||||
SubmenuIndexWriteCopyKey,
|
||||
nfc_protocol_support_common_submenu_callback,
|
||||
instance);
|
||||
}
|
||||
|
||||
if(is_locked) {
|
||||
@@ -291,6 +311,14 @@ static bool nfc_scene_read_and_saved_menu_on_event_mf_ultralight(
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightCDictAttack);
|
||||
}
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteKeepKey) {
|
||||
instance->mf_ultralight_c_write_context.copy_key = false;
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteCopyKey) {
|
||||
instance->mf_ultralight_c_write_context.copy_key = true;
|
||||
scene_manager_next_scene(instance->scene_manager, NfcSceneWrite);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
@@ -307,12 +335,139 @@ static NfcCommand
|
||||
if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestMode) {
|
||||
mf_ultralight_event->data->poller_mode = MfUltralightPollerModeWrite;
|
||||
furi_string_reset(instance->text_box_store);
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
}
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
|
||||
// Skip auth during the read phase of write - we'll authenticate
|
||||
// against the target card in RequestWriteData using source key or dict attack
|
||||
mf_ultralight_event->data->auth_context.skip_auth = true;
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestKey) {
|
||||
// Dict attack key provider - user dict first, then system dict
|
||||
if(!instance->mf_ultralight_c_dict_context.dict &&
|
||||
instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictIdle) {
|
||||
if(keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) {
|
||||
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
|
||||
NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH,
|
||||
KeysDictModeOpenExisting,
|
||||
sizeof(MfUltralightC3DesAuthKey));
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictUser;
|
||||
}
|
||||
if(!instance->mf_ultralight_c_dict_context.dict) {
|
||||
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
|
||||
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
|
||||
KeysDictModeOpenExisting,
|
||||
sizeof(MfUltralightC3DesAuthKey));
|
||||
instance->mf_ultralight_c_write_context.dict_state =
|
||||
NfcMfUltralightCWriteDictSystem;
|
||||
}
|
||||
}
|
||||
MfUltralightC3DesAuthKey key = {};
|
||||
bool got_key = false;
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
got_key = keys_dict_get_next_key(
|
||||
instance->mf_ultralight_c_dict_context.dict,
|
||||
key.data,
|
||||
sizeof(MfUltralightC3DesAuthKey));
|
||||
}
|
||||
if(!got_key &&
|
||||
instance->mf_ultralight_c_write_context.dict_state == NfcMfUltralightCWriteDictUser) {
|
||||
// Exhausted user dict, switch to system dict
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
}
|
||||
instance->mf_ultralight_c_dict_context.dict = keys_dict_alloc(
|
||||
NFC_APP_MF_ULTRALIGHT_C_DICT_SYSTEM_PATH,
|
||||
KeysDictModeOpenExisting,
|
||||
sizeof(MfUltralightC3DesAuthKey));
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictSystem;
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
got_key = keys_dict_get_next_key(
|
||||
instance->mf_ultralight_c_dict_context.dict,
|
||||
key.data,
|
||||
sizeof(MfUltralightC3DesAuthKey));
|
||||
}
|
||||
}
|
||||
if(got_key) {
|
||||
mf_ultralight_event->data->key_request_data.key = key;
|
||||
mf_ultralight_event->data->key_request_data.key_provided = true;
|
||||
FURI_LOG_D(
|
||||
"MfULC",
|
||||
"Trying dict key: "
|
||||
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
key.data[0],
|
||||
key.data[1],
|
||||
key.data[2],
|
||||
key.data[3],
|
||||
key.data[4],
|
||||
key.data[5],
|
||||
key.data[6],
|
||||
key.data[7],
|
||||
key.data[8],
|
||||
key.data[9],
|
||||
key.data[10],
|
||||
key.data[11],
|
||||
key.data[12],
|
||||
key.data[13],
|
||||
key.data[14],
|
||||
key.data[15]);
|
||||
} else {
|
||||
mf_ultralight_event->data->key_request_data.key_provided = false;
|
||||
FURI_LOG_D("MfULC", "Dict exhausted - no more keys");
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
}
|
||||
instance->mf_ultralight_c_write_context.dict_state =
|
||||
NfcMfUltralightCWriteDictExhausted;
|
||||
}
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeRequestWriteData) {
|
||||
mf_ultralight_event->data->write_data =
|
||||
nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
|
||||
// Reset dict context so RequestKey starts fresh for the write-phase auth
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
}
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteKeyRequest) {
|
||||
// Apply the user's key choice - read from static, not scene state (scene manager
|
||||
// resets state to 0 on scene entry, wiping any value set before next_scene).
|
||||
bool keep_key = !instance->mf_ultralight_c_write_context.copy_key;
|
||||
mf_ultralight_event->data->write_key_skip = keep_key;
|
||||
|
||||
if(mf_ultralight_event->data->key_request_data.key_provided) {
|
||||
MfUltralightC3DesAuthKey found_key = mf_ultralight_event->data->key_request_data.key;
|
||||
FURI_LOG_D(
|
||||
"MfULC",
|
||||
"WriteKeyRequest: target key = "
|
||||
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
|
||||
found_key.data[0],
|
||||
found_key.data[1],
|
||||
found_key.data[2],
|
||||
found_key.data[3],
|
||||
found_key.data[4],
|
||||
found_key.data[5],
|
||||
found_key.data[6],
|
||||
found_key.data[7],
|
||||
found_key.data[8],
|
||||
found_key.data[9],
|
||||
found_key.data[10],
|
||||
found_key.data[11],
|
||||
found_key.data[12],
|
||||
found_key.data[13],
|
||||
found_key.data[14],
|
||||
found_key.data[15]);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
"MfULC",
|
||||
"WriteKeyRequest: decision = %s (copy_key=%d)",
|
||||
keep_key ? "KEEP target key (pages 44-47 NOT written)" :
|
||||
"OVERWRITE with source key (pages 44-47 WILL be written)",
|
||||
(int)instance->mf_ultralight_c_write_context.copy_key);
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeCardMismatch) {
|
||||
furi_string_set(instance->text_box_store, "Card of the same\ntype should be\n presented");
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWrongCard);
|
||||
@@ -323,6 +478,7 @@ static NfcCommand
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
command = NfcCommandStop;
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteFail) {
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
command = NfcCommandStop;
|
||||
} else if(mf_ultralight_event->type == MfUltralightPollerEventTypeWriteSuccess) {
|
||||
furi_string_reset(instance->text_box_store);
|
||||
@@ -334,9 +490,18 @@ static NfcCommand
|
||||
}
|
||||
|
||||
static void nfc_scene_write_on_enter_mf_ultralight(NfcApp* instance) {
|
||||
// Free any dict the write callback opened (dict_state != Idle means we own it).
|
||||
// After a DictAttack scene, on_exit now NULLs the pointer so a simple NULL check
|
||||
// is safe here too — but the state enum is the authoritative ownership record.
|
||||
if(instance->mf_ultralight_c_write_context.dict_state != NfcMfUltralightCWriteDictIdle &&
|
||||
instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
}
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
|
||||
furi_string_set(instance->text_box_store, "\nApply the\ntarget\ncard now");
|
||||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfUltralight);
|
||||
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_mf_ultralight, instance);
|
||||
furi_string_set(instance->text_box_store, "Apply the initial\ncard only");
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "../nfc_protocol_support_common.h"
|
||||
#include "../nfc_protocol_support_gui_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "SlixWrite"
|
||||
|
||||
static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
|
||||
const NfcDevice* device = instance->nfc_device;
|
||||
const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
|
||||
@@ -106,8 +110,104 @@ static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
|
||||
nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
|
||||
}
|
||||
|
||||
static NfcCommand nfc_scene_write_poller_callback_slix(NfcGenericEvent event, void* context) {
|
||||
furi_assert(event.protocol == NfcProtocolSlix);
|
||||
|
||||
NfcApp* instance = context;
|
||||
SlixPoller* poller = event.instance;
|
||||
const SlixPollerEvent* slix_event = event.event_data;
|
||||
NfcCommand command = NfcCommandContinue;
|
||||
|
||||
if(slix_event->type == SlixPollerEventTypeReady) {
|
||||
const SlixData* write_data = nfc_device_get_data(instance->nfc_device, NfcProtocolSlix);
|
||||
const Iso15693_3Data* write_iso_data = slix_get_base_data(write_data);
|
||||
const Iso15693_3Data* card_iso_data =
|
||||
slix_get_base_data((const SlixData*)nfc_poller_get_data(instance->poller));
|
||||
|
||||
view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventCardDetected);
|
||||
|
||||
if(card_iso_data->system_info.block_count != write_iso_data->system_info.block_count ||
|
||||
card_iso_data->system_info.block_size != write_iso_data->system_info.block_size) {
|
||||
furi_string_set(
|
||||
instance->text_box_store, "Incompatible card\n(block count/size\nmismatch)");
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
} else {
|
||||
SlixError error = SlixErrorNone;
|
||||
uint16_t failed_block = 0;
|
||||
const uint8_t block_size = write_iso_data->system_info.block_size;
|
||||
const uint8_t* write_data_ptr =
|
||||
(const uint8_t*)simple_array_cget_data(write_iso_data->block_data);
|
||||
const uint8_t* card_data_ptr =
|
||||
(const uint8_t*)simple_array_cget_data(card_iso_data->block_data);
|
||||
for(uint16_t i = 0; i < write_iso_data->system_info.block_count; i++) {
|
||||
if(iso15693_3_is_block_locked(write_iso_data, i)) continue;
|
||||
// Skip blocks that already contain the correct data
|
||||
if(memcmp(
|
||||
write_data_ptr + i * block_size,
|
||||
card_data_ptr + i * block_size,
|
||||
block_size) == 0)
|
||||
continue;
|
||||
error = slix_poller_write_block(
|
||||
poller, write_data_ptr + i * block_size, i, block_size);
|
||||
if(error == SlixErrorInternal) {
|
||||
// Block is locked on the target card, skip it
|
||||
error = SlixErrorNone;
|
||||
continue;
|
||||
}
|
||||
if(error != SlixErrorNone) {
|
||||
failed_block = i;
|
||||
FURI_LOG_E(TAG, "Write failed: block %u, error %d", failed_block, (int)error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(error == SlixErrorNone) {
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerSuccess);
|
||||
} else {
|
||||
const char* err_name;
|
||||
switch(error) {
|
||||
case SlixErrorTimeout:
|
||||
err_name = "Timeout";
|
||||
break;
|
||||
case SlixErrorFormat:
|
||||
err_name = "BadCRC";
|
||||
break;
|
||||
case SlixErrorNotSupported:
|
||||
err_name = "NotSupported";
|
||||
break;
|
||||
case SlixErrorInternal:
|
||||
err_name = "Locked";
|
||||
break;
|
||||
case SlixErrorWrongPassword:
|
||||
err_name = "WrongPwd";
|
||||
break;
|
||||
default:
|
||||
err_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
furi_string_printf(
|
||||
instance->text_box_store, "Block %u: %s", failed_block, err_name);
|
||||
view_dispatcher_send_custom_event(
|
||||
instance->view_dispatcher, NfcCustomEventPollerFailure);
|
||||
}
|
||||
}
|
||||
command = NfcCommandStop;
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
static void nfc_scene_write_on_enter_slix(NfcApp* instance) {
|
||||
furi_string_set(instance->text_box_store, "Apply the\ntarget card now");
|
||||
instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolSlix);
|
||||
nfc_poller_start(instance->poller, nfc_scene_write_poller_callback_slix, instance);
|
||||
}
|
||||
|
||||
const NfcProtocolSupportBase nfc_protocol_support_slix = {
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
|
||||
.features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo |
|
||||
NfcProtocolFeatureWrite,
|
||||
|
||||
.scene_info =
|
||||
{
|
||||
@@ -151,7 +251,7 @@ const NfcProtocolSupportBase nfc_protocol_support_slix = {
|
||||
},
|
||||
.scene_write =
|
||||
{
|
||||
.on_enter = nfc_protocol_support_common_on_enter_empty,
|
||||
.on_enter = nfc_scene_write_on_enter_slix,
|
||||
.on_event = nfc_protocol_support_common_on_event_empty,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -126,6 +126,18 @@ typedef struct {
|
||||
size_t dict_keys_current;
|
||||
} NfcMfUltralightCDictContext;
|
||||
|
||||
typedef enum {
|
||||
NfcMfUltralightCWriteDictIdle, /**< No dict open; safe to open either dict. */
|
||||
NfcMfUltralightCWriteDictUser, /**< User dict currently open. */
|
||||
NfcMfUltralightCWriteDictSystem, /**< System dict currently open. */
|
||||
NfcMfUltralightCWriteDictExhausted, /**< All dicts tried; do not re-open. */
|
||||
} NfcMfUltralightCWriteDictState;
|
||||
|
||||
typedef struct {
|
||||
bool copy_key; /**< True = overwrite target 3DES key with source key pages. */
|
||||
NfcMfUltralightCWriteDictState dict_state; /**< Which dict is open for write-phase auth. */
|
||||
} NfcMfUltralightCWriteContext;
|
||||
|
||||
struct NfcApp {
|
||||
DialogsApp* dialogs;
|
||||
Storage* storage;
|
||||
@@ -165,6 +177,7 @@ struct NfcApp {
|
||||
SlixUnlock* slix_unlock;
|
||||
NfcMfClassicDictAttackContext nfc_dict_context;
|
||||
NfcMfUltralightCDictContext mf_ultralight_c_dict_context;
|
||||
NfcMfUltralightCWriteContext mf_ultralight_c_write_context;
|
||||
Mfkey32Logger* mfkey32_logger;
|
||||
MfUserDict* mf_user_dict;
|
||||
MfClassicKeyCache* mfc_key_cache;
|
||||
|
||||
@@ -1,23 +1,21 @@
|
||||
//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL (<me@willyjl.dev>) for help!
|
||||
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
|
||||
#include <flipper_application/flipper_application.h>
|
||||
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
|
||||
#define TAG "Plantain"
|
||||
|
||||
void from_minutes_to_datetime(uint32_t minutes, DateTime* datetime, uint16_t start_year) {
|
||||
uint32_t timestamp = minutes * 60;
|
||||
DateTime start_datetime = {0};
|
||||
start_datetime.year = start_year - 1;
|
||||
start_datetime.month = 12;
|
||||
start_datetime.day = 31;
|
||||
timestamp += datetime_datetime_to_timestamp(&start_datetime);
|
||||
datetime_timestamp_to_datetime(timestamp, datetime);
|
||||
}
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#define PLANTAIN_EPOCH_START 1262304000 //2010-01-01
|
||||
#define PPK_WHOLE_EPOCH_START 946684800 //2000-01-01
|
||||
#define PPK_CURRENT_EPOCH_START 1388534400 //2014-01-01
|
||||
#define SECONDS_IN_A_DAY 86400
|
||||
#define SECONDS_IN_A_MINUTE 60
|
||||
#define FIRST_PPK_TICKET_OFFSET 101
|
||||
#define FIRST_TICKET_VALUE_BLOCK 104
|
||||
#define SECOND_PPK_TICKET_OFFSET 102
|
||||
#define SECOND_TICKET_VALUE_BLOCK 108
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
@@ -30,79 +28,549 @@ typedef struct {
|
||||
} PlantainCardConfig;
|
||||
|
||||
static const MfClassicKeyPair plantain_1k_keys[] = {
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b},
|
||||
{.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7},
|
||||
{.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb},
|
||||
{.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //0
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //1
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //2
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //3
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, //4
|
||||
{.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, //5
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //6
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //7
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, //8
|
||||
{.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, //9
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, //10
|
||||
{.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, //11
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, //12
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //13
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //14
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //15
|
||||
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, //16
|
||||
{.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, //17
|
||||
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, //18
|
||||
{.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, //19
|
||||
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, //20
|
||||
{.a = 0x0f318130ed18, .b = 0x0c420a20e056}, //21
|
||||
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, //22
|
||||
{.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, //23
|
||||
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, //24
|
||||
{.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, //25
|
||||
{.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, //26
|
||||
{.a = 0x7413b599c4ea, .b = 0xb0a2aaf3a1ba}, //27
|
||||
{.a = 0x7413b599c4ea, .b = 0xb0a2aaf3a1ba}, //28
|
||||
{.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, //29
|
||||
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, //30
|
||||
{.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, //31
|
||||
|
||||
};
|
||||
|
||||
static const MfClassicKeyPair plantain_4k_keys[] = {
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a},
|
||||
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3},
|
||||
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056},
|
||||
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf},
|
||||
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406},
|
||||
{.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA},
|
||||
{.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3},
|
||||
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290},
|
||||
{.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9},
|
||||
{.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461},
|
||||
{.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a},
|
||||
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //0
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //1
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //2
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //3
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, //4
|
||||
{.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, //5
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //6
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //7
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, //8
|
||||
{.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, //9
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, //10
|
||||
{.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, //11
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, //12
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //13
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //14
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //15
|
||||
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, //16
|
||||
{.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, //17
|
||||
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, //18
|
||||
{.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, //19
|
||||
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, //20
|
||||
{.a = 0x0f318130ed18, .b = 0x0c420a20e056}, //21
|
||||
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, //22
|
||||
{.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, //23
|
||||
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, //24
|
||||
{.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406}, //25
|
||||
{.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, //26
|
||||
{.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA}, //27
|
||||
{.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, //28
|
||||
{.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3}, //29
|
||||
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, //30
|
||||
{.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, //31
|
||||
{.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, //32
|
||||
{.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, //33
|
||||
{.a = 0xfd8705e721b0, .b = 0x296fc317a513}, //34
|
||||
{.a = 0x22052b480d11, .b = 0xe19504c39461}, //35
|
||||
{.a = 0xa7141147d430, .b = 0xff16014fefc7}, //36
|
||||
{.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, //37
|
||||
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, //38
|
||||
{.a = 0x7259fa0197c6, .b = 0x5583698df085}, //39
|
||||
};
|
||||
|
||||
static const MfClassicKeyPair plantain_4k_keys_legacy[] = {
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
|
||||
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a},
|
||||
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3},
|
||||
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056},
|
||||
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf},
|
||||
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x46d78e850a7e, .b = 0xa470f8130991},
|
||||
{.a = 0x42e9b54e51ab, .b = 0x0231b86df52e}, {.a = 0x0f01ceff2742, .b = 0x6fec74559ca7},
|
||||
{.a = 0xb81f2b0c2f66, .b = 0xa7e2d95f0003}, {.a = 0x9ea3387a63c1, .b = 0x437e59f57561},
|
||||
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290},
|
||||
{.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9},
|
||||
{.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461},
|
||||
{.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a},
|
||||
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //0
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //1
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //2
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //3
|
||||
{.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, //4
|
||||
{.a = 0x77dabc9825e1, .b = 0x9764fec3154a}, //5
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //6
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //7
|
||||
{.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, //8
|
||||
{.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3}, //9
|
||||
{.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, //10
|
||||
{.a = 0xc76bf71a2509, .b = 0x9ba241db3f56}, //11
|
||||
{.a = 0xacffffffffff, .b = 0x71f3a315ad26}, //12
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //13
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //14
|
||||
{.a = 0xffffffffffff, .b = 0xffffffffffff}, //15
|
||||
{.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, //16
|
||||
{.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a}, //17
|
||||
{.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, //18
|
||||
{.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3}, //19
|
||||
{.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, //20
|
||||
{.a = 0x0f318130ed18, .b = 0x0c420a20e056}, //21
|
||||
{.a = 0x045ceca15535, .b = 0x31bec3d9e510}, //22
|
||||
{.a = 0x9d993c5d4ef4, .b = 0x86120e488abf}, //23
|
||||
{.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, //24
|
||||
{.a = 0x46d78e850a7e, .b = 0xa470f8130991}, //25
|
||||
{.a = 0x42e9b54e51ab, .b = 0x0231b86df52e}, //26
|
||||
{.a = 0x0f01ceff2742, .b = 0x6fec74559ca7}, //27
|
||||
{.a = 0xb81f2b0c2f66, .b = 0xa7e2d95f0003}, //28
|
||||
{.a = 0x9ea3387a63c1, .b = 0x437e59f57561}, //29
|
||||
{.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, //30
|
||||
{.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290}, //31
|
||||
{.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, //32
|
||||
{.a = 0xa3faa6daff67, .b = 0x7600e889adf9}, //33
|
||||
{.a = 0xfd8705e721b0, .b = 0x296fc317a513}, //34
|
||||
{.a = 0x22052b480d11, .b = 0xe19504c39461}, //35
|
||||
{.a = 0xa7141147d430, .b = 0xff16014fefc7}, //36
|
||||
{.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a}, //37
|
||||
{.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, //38
|
||||
{.a = 0x7259fa0197c6, .b = 0x5583698df085}, //39
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint16_t departure_uic;
|
||||
uint16_t destination_uic;
|
||||
uint16_t trip_start_uic;
|
||||
uint16_t trip_end_uic;
|
||||
FuriString* departure_name;
|
||||
FuriString* destination_name;
|
||||
FuriString* trip_start_sta_name;
|
||||
FuriString* trip_end_sta_name;
|
||||
uint8_t value_data;
|
||||
uint8_t direction;
|
||||
uint8_t current_status;
|
||||
uint16_t valid_from_data;
|
||||
uint16_t valid_till_data;
|
||||
DateTime valid_from;
|
||||
DateTime valid_till;
|
||||
uint32_t tap_data;
|
||||
DateTime tap_time;
|
||||
uint8_t first_ticket_marker;
|
||||
uint8_t second_ticket_marker;
|
||||
uint64_t sys_n;
|
||||
uint8_t ppk_cnt;
|
||||
} PPKData;
|
||||
|
||||
typedef struct {
|
||||
uint64_t card_number;
|
||||
FuriString* card_number_str;
|
||||
uint32_t balance;
|
||||
uint8_t trips_metro;
|
||||
uint8_t trips_ground;
|
||||
uint32_t last_trip_data;
|
||||
DateTime last_trip_time;
|
||||
uint16_t validator;
|
||||
uint16_t fare;
|
||||
uint32_t last_payment_date_data;
|
||||
DateTime last_payment_date;
|
||||
uint16_t last_payment_amount;
|
||||
uint8_t keyset;
|
||||
|
||||
} PlantainData;
|
||||
// Function to map UIC codes to station names
|
||||
static inline void sz_uic_to_sta(Storage* storage, const char* file_name, PPKData* ticket) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* departure_uic = furi_string_alloc_printf("%04X", ticket->departure_uic);
|
||||
FuriString* destination_uic = furi_string_alloc_printf("%04X", ticket->destination_uic);
|
||||
FuriString* trip_start_uic = furi_string_alloc_printf("%04X", ticket->trip_start_uic);
|
||||
FuriString* trip_end_uic = furi_string_alloc_printf("%04X", ticket->trip_end_uic);
|
||||
|
||||
if(flipper_format_file_open_existing(file, file_name)) {
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(departure_uic), ticket->departure_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(destination_uic), ticket->destination_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_start_uic), ticket->trip_start_sta_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_end_uic), ticket->trip_end_sta_name);
|
||||
}
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_string_free(departure_uic);
|
||||
furi_string_free(destination_uic);
|
||||
furi_string_free(trip_start_uic);
|
||||
furi_string_free(trip_end_uic);
|
||||
}
|
||||
// Function to resolve station names for a ticket, and if not found, set to "1E" + UIC code
|
||||
static void resolve_station_name(Storage* storage, PPKData* ticket) {
|
||||
sz_uic_to_sta(storage, EXT_PATH("nfc/assets/sz_id.nfc"), ticket);
|
||||
if(furi_string_utf8_length(ticket->departure_name) <= 2) {
|
||||
furi_string_printf(ticket->departure_name, "1E%04X", ticket->departure_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->destination_name) <= 2) {
|
||||
furi_string_printf(ticket->destination_name, "1E%04X", ticket->destination_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_start_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_start_sta_name, "1E%04X", ticket->trip_start_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_end_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_end_sta_name, "1E%04X", ticket->trip_end_uic);
|
||||
}
|
||||
}
|
||||
// Function to extract plantain purse data from the card data
|
||||
static inline void extract_purse_data(
|
||||
const MfClassicData* data,
|
||||
PlantainData* purse,
|
||||
PPKData* ticket,
|
||||
uint8_t first_ppk_ticket_offset,
|
||||
uint8_t second_ppk_ticket_offset) {
|
||||
uint64_t card_number = 0;
|
||||
size_t uid_len = 0;
|
||||
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||
purse->card_number_str = furi_string_alloc();
|
||||
const uint8_t* temp_ptr = &uid[0];
|
||||
uint8_t card_number_tmp[uid_len];
|
||||
|
||||
if(uid_len == 4) {
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
card_number_tmp[i] = temp_ptr[3 - i];
|
||||
}
|
||||
} else if(uid_len == 7) {
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_tmp[i] = temp_ptr[6 - i];
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < uid_len; i++) {
|
||||
card_number = (card_number << 8) | card_number_tmp[i];
|
||||
}
|
||||
FuriString* card_number_s = furi_string_alloc();
|
||||
furi_string_cat_printf(card_number_s, "%lld", card_number);
|
||||
FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 ");
|
||||
for(uint8_t i = 0; i < 24; i += 4) {
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j));
|
||||
}
|
||||
furi_string_push_back(tmp_s, ' ');
|
||||
}
|
||||
|
||||
furi_string_set(purse->card_number_str, furi_string_get_cstr(tmp_s));
|
||||
furi_string_free(card_number_s);
|
||||
furi_string_free(tmp_s);
|
||||
|
||||
if(data->type == MfClassicType1k) {
|
||||
uint32_t balance = 0;
|
||||
for(uint8_t i = 0; i < 4; i++)
|
||||
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||
balance /= 100;
|
||||
purse->balance = balance;
|
||||
purse->trips_metro = data->block[21].data[0];
|
||||
purse->trips_ground = data->block[21].data[1];
|
||||
uint32_t last_trip_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
purse->last_trip_data = (purse->last_trip_data << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
purse->validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||
uint16_t fare = ((data->block[20].data[7] << 8) | data->block[20].data[6]) / 100;
|
||||
purse->fare = fare;
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
purse->last_payment_date_data = (purse->last_payment_date_data << 8) |
|
||||
data->block[18].data[4 - i];
|
||||
}
|
||||
purse->last_payment_amount = ((data->block[18].data[10] << 16) |
|
||||
(data->block[18].data[9] << 8) | (data->block[18].data[8])) /
|
||||
100;
|
||||
last_trip_timestamp = PLANTAIN_EPOCH_START + purse->last_trip_data * SECONDS_IN_A_MINUTE;
|
||||
const uint32_t last_payment_timestamp =
|
||||
PLANTAIN_EPOCH_START + purse->last_payment_date_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(last_trip_timestamp, &purse->last_trip_time);
|
||||
datetime_timestamp_to_datetime(last_payment_timestamp, &purse->last_payment_date);
|
||||
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
uint32_t balance = 0;
|
||||
for(uint8_t i = 0; i < 4; i++)
|
||||
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||
balance /= 100;
|
||||
purse->balance = balance;
|
||||
purse->trips_metro = data->block[21].data[0];
|
||||
purse->trips_ground = data->block[21].data[1];
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
purse->last_trip_data = (purse->last_trip_data << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
purse->validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||
uint16_t fare = ((data->block[20].data[7] << 8) | data->block[20].data[6]) / 100;
|
||||
purse->fare = fare;
|
||||
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
purse->last_payment_date_data = (purse->last_payment_date_data << 8) |
|
||||
data->block[18].data[4 - i];
|
||||
}
|
||||
purse->last_payment_amount = ((data->block[18].data[10] << 16) |
|
||||
(data->block[18].data[9] << 8) | (data->block[18].data[8])) /
|
||||
100;
|
||||
uint32_t last_trip_timestamp =
|
||||
PLANTAIN_EPOCH_START + purse->last_trip_data * SECONDS_IN_A_MINUTE;
|
||||
const uint32_t last_payment_timestamp =
|
||||
PLANTAIN_EPOCH_START + purse->last_payment_date_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(last_trip_timestamp, &purse->last_trip_time);
|
||||
datetime_timestamp_to_datetime(last_payment_timestamp, &purse->last_payment_date);
|
||||
}
|
||||
|
||||
ticket->first_ticket_marker = data->block[first_ppk_ticket_offset].data[0];
|
||||
ticket->second_ticket_marker = data->block[second_ppk_ticket_offset].data[0];
|
||||
}
|
||||
// Function to extract PPK ticket data from the card data
|
||||
static inline void extract_ppk_data(
|
||||
Storage* storage,
|
||||
const MfClassicData* data,
|
||||
PPKData* ticket,
|
||||
bool second_ticket) {
|
||||
const uint8_t* temp_ptr = &data->block[SECOND_TICKET_VALUE_BLOCK + 2].data[0];
|
||||
uint8_t sys_n_arr[6] = {0};
|
||||
|
||||
if(second_ticket == 0) {
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
sys_n_arr[i] = temp_ptr[7 - i];
|
||||
}
|
||||
ticket->departure_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[FIRST_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[3]);
|
||||
|
||||
ticket->direction = data->block[FIRST_PPK_TICKET_OFFSET].data[14];
|
||||
ticket->tap_data = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[105].data[10];
|
||||
ticket->trip_start_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
|
||||
} else if(second_ticket == 1) {
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
sys_n_arr[i] = temp_ptr[13 - i];
|
||||
}
|
||||
ticket->departure_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[SECOND_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[3]);
|
||||
|
||||
ticket->direction = data->block[SECOND_PPK_TICKET_OFFSET].data[14];
|
||||
ticket->tap_data = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[109].data[10];
|
||||
ticket->trip_start_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
}
|
||||
ticket->first_ticket_marker = data->block[FIRST_PPK_TICKET_OFFSET].data[0];
|
||||
ticket->second_ticket_marker = data->block[SECOND_PPK_TICKET_OFFSET].data[0];
|
||||
uint64_t sys_n = 0;
|
||||
for(size_t i = 0; i < 6; i++) {
|
||||
sys_n = (sys_n << 8) | sys_n_arr[i];
|
||||
}
|
||||
ticket->sys_n = sys_n;
|
||||
|
||||
const uint32_t valid_from_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_from_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t valid_till_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_till_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t tap_timestamp =
|
||||
PPK_CURRENT_EPOCH_START + ticket->tap_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(valid_from_timestamp, &ticket->valid_from);
|
||||
datetime_timestamp_to_datetime(valid_till_timestamp, &ticket->valid_till);
|
||||
datetime_timestamp_to_datetime(tap_timestamp, &ticket->tap_time);
|
||||
resolve_station_name(storage, ticket);
|
||||
}
|
||||
// Function to format and print plantain purse data
|
||||
static void printf_plantain_data(FuriString* parsed_data, PlantainData* purse) {
|
||||
furi_string_printf(parsed_data, "\e#Plantain Card");
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nNumber: %s\nBalance: %ld RUB\nMetro Trips: %d\nGround Trips: %d\nLast Trip: %02d.%02d.%04d %02d:%02d",
|
||||
furi_string_get_cstr(purse->card_number_str),
|
||||
purse->balance,
|
||||
purse->trips_metro,
|
||||
purse->trips_ground,
|
||||
purse->last_trip_time.day,
|
||||
purse->last_trip_time.month,
|
||||
purse->last_trip_time.year,
|
||||
purse->last_trip_time.hour,
|
||||
purse->last_trip_time.minute);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nValidator: %d\nFare: %d RUB\nRefilled on: %02d.%02d.%04d %02d:%02d\nAmount: %d RUB",
|
||||
purse->validator,
|
||||
purse->fare,
|
||||
purse->last_payment_date.day,
|
||||
purse->last_payment_date.month,
|
||||
purse->last_payment_date.year,
|
||||
purse->last_payment_date.hour,
|
||||
purse->last_payment_date.minute,
|
||||
purse->last_payment_amount);
|
||||
|
||||
if(purse->keyset == 1)
|
||||
furi_string_cat_printf(parsed_data, "\nPPK keys installed:> YES");
|
||||
else
|
||||
furi_string_cat_printf(parsed_data, "\nPPK keys installed:> NO");
|
||||
|
||||
furi_string_free(purse->card_number_str);
|
||||
}
|
||||
|
||||
// Function to format and print PPK ticket data
|
||||
static void printf_ppk_data(FuriString* parsed_data, PPKData* ticket, bool ticket_number) {
|
||||
if(ticket_number == 0) {
|
||||
furi_string_cat_printf(parsed_data, "\n\n\e#PPK Ticket:\n");
|
||||
switch(ticket->first_ticket_marker) {
|
||||
case 0x33:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 1 ride");
|
||||
break;
|
||||
case 0x34:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Mon.-Fri.)");
|
||||
break;
|
||||
case 0x35:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Fri.-Mon.)");
|
||||
break;
|
||||
case 0x36:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Sat.-Mon.");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type:> Unknown, 0x%02X", ticket->first_ticket_marker);
|
||||
}
|
||||
} else if(ticket_number == 1) {
|
||||
furi_string_cat_printf(parsed_data, "\n\nSecond PPK Ticket:\n");
|
||||
switch(ticket->second_ticket_marker) {
|
||||
case 0x33:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 1 ride");
|
||||
break;
|
||||
case 0x34:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Mon.-Fri.)");
|
||||
break;
|
||||
case 0x35:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Fri.-Mon.)");
|
||||
break;
|
||||
case 0x36:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 2 rides (Sat.-Mon.)");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type:> Unknown, 0x%02X", ticket->second_ticket_marker);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nFrom:> %s\nTo:> %s",
|
||||
furi_string_get_cstr(ticket->departure_name),
|
||||
furi_string_get_cstr(ticket->destination_name));
|
||||
|
||||
if(ticket->valid_from.day == ticket->valid_till.day) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nValid On: %02d-%02d-%04d",
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year);
|
||||
} else {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nValid From: %02d-%02d-%04d\nValid thru: %02d-%02d-%04d",
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year,
|
||||
ticket->valid_till.day,
|
||||
ticket->valid_till.month,
|
||||
ticket->valid_till.year);
|
||||
}
|
||||
|
||||
if(ticket->direction == 1) {
|
||||
furi_string_cat_printf(parsed_data, "\nDirection: One-way ->>");
|
||||
} else if(ticket->direction == 2) {
|
||||
furi_string_cat_printf(parsed_data, "\nDirection: Round-trip <<-->>");
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "\nRides left:> %02d", ticket->value_data);
|
||||
|
||||
if(ticket->current_status == 0) {
|
||||
furi_string_cat_printf(parsed_data, "\nStatus:> TICKET IS READY\n");
|
||||
} else if(ticket->current_status == 0x80)
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nStatus:> ENTERED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\n",
|
||||
furi_string_get_cstr(ticket->trip_start_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute);
|
||||
else if(ticket->current_status == 0x1E)
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nStatus:> EXITED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\n",
|
||||
furi_string_get_cstr(ticket->trip_end_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute);
|
||||
else
|
||||
furi_string_cat_printf(parsed_data, "\nStatus:> UNKNOWN (%02X)\n", ticket->current_status);
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "SYS N:> %lld\nPPK CNT:> %03d", ticket->sys_n, ticket->ppk_cnt);
|
||||
}
|
||||
//Function to select a keyset based on card type
|
||||
static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) {
|
||||
bool success = true;
|
||||
|
||||
if(type == MfClassicType1k) {
|
||||
config->data_sector = 8;
|
||||
config->keys = plantain_1k_keys;
|
||||
|
||||
} else if(type == MfClassicType4k) {
|
||||
config->data_sector = 8;
|
||||
config->keys = plantain_4k_keys;
|
||||
|
||||
} else {
|
||||
success = false;
|
||||
}
|
||||
@@ -114,11 +582,10 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
PlantainCardConfig cfg = {};
|
||||
PlantainCardConfig cfg = {0};
|
||||
if(!plantain_get_card_config(&cfg, type)) break;
|
||||
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
|
||||
|
||||
MfClassicKey key = {0};
|
||||
bit_lib_num_to_bytes_be(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
|
||||
@@ -127,7 +594,6 @@ static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
|
||||
if(error != MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -157,7 +623,7 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
if(error != MfClassicErrorNone) break;
|
||||
|
||||
data->type = type;
|
||||
PlantainCardConfig cfg = {};
|
||||
PlantainCardConfig cfg = {0};
|
||||
if(!plantain_get_card_config(&cfg, data->type)) break;
|
||||
|
||||
const uint8_t legacy_check_sec_num = 26;
|
||||
@@ -171,21 +637,28 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
error = mf_classic_poller_sync_auth(
|
||||
nfc, legacy_check_block_num, &key, MfClassicKeyTypeA, NULL);
|
||||
if(error == MfClassicErrorNone) {
|
||||
FURI_LOG_D(TAG, "Legacy keys detected");
|
||||
cfg.keys = plantain_4k_keys_legacy;
|
||||
}
|
||||
|
||||
MfClassicDeviceKeys keys = {};
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
if(data->type == MfClassicType1k) {
|
||||
for(size_t i = 0; i < 32; i++) {
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
} else {
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error == MfClassicErrorNotPresent) {
|
||||
FURI_LOG_W(TAG, "Failed to read data");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -198,18 +671,22 @@ static bool plantain_read(Nfc* nfc, NfcDevice* device) {
|
||||
|
||||
return is_read;
|
||||
}
|
||||
|
||||
//Main parsing function
|
||||
static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
size_t uid_len = 0;
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
|
||||
|
||||
PPKData ticket = {0};
|
||||
ticket.departure_name = furi_string_alloc();
|
||||
ticket.destination_name = furi_string_alloc();
|
||||
ticket.trip_start_sta_name = furi_string_alloc();
|
||||
ticket.trip_end_sta_name = furi_string_alloc();
|
||||
PlantainData purse = {0};
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
// Verify card type
|
||||
PlantainCardConfig cfg = {};
|
||||
PlantainCardConfig cfg = {0};
|
||||
if(!plantain_get_card_config(&cfg, data->type)) break;
|
||||
|
||||
// Verify key
|
||||
@@ -220,167 +697,47 @@ static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
|
||||
if(key != cfg.keys[cfg.data_sector].a) break;
|
||||
|
||||
furi_string_printf(parsed_data, "\e#Plantain card\n");
|
||||
if(data->block[107].data[10] == 0x02)
|
||||
purse.keyset = 0;
|
||||
else
|
||||
purse.keyset = 1;
|
||||
|
||||
const uint8_t* temp_ptr = &uid[0];
|
||||
|
||||
// UID is read from last to first byte
|
||||
uint8_t card_number_tmp[uid_len];
|
||||
|
||||
if(uid_len == 4) {
|
||||
for(size_t i = 0; i < 4; i++) {
|
||||
card_number_tmp[i] = temp_ptr[3 - i];
|
||||
}
|
||||
} else if(uid_len == 7) {
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
card_number_tmp[i] = temp_ptr[6 - i];
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
// Extract plantain purse data and fill PPK tickets markers
|
||||
extract_purse_data(
|
||||
data, &purse, &ticket, FIRST_PPK_TICKET_OFFSET, SECOND_PPK_TICKET_OFFSET);
|
||||
// Print plantain purse data
|
||||
printf_plantain_data(parsed_data, &purse);
|
||||
if(purse.card_number_str) furi_string_free(purse.card_number_str);
|
||||
// Extract and print PPK ticket data if present
|
||||
if(ticket.first_ticket_marker != 0) {
|
||||
extract_ppk_data(storage, data, &ticket, 0);
|
||||
printf_ppk_data(parsed_data, &ticket, false);
|
||||
}
|
||||
//UID is converted to a card number
|
||||
uint64_t card_number = 0;
|
||||
for(size_t i = 0; i < uid_len; i++) {
|
||||
card_number = (card_number << 8) | card_number_tmp[i];
|
||||
// Extract and print second PPK ticket data if present
|
||||
if(ticket.second_ticket_marker != 0) {
|
||||
PPKData second_ticket = {0};
|
||||
second_ticket.departure_name = furi_string_alloc();
|
||||
second_ticket.destination_name = furi_string_alloc();
|
||||
second_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
second_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
|
||||
extract_ppk_data(storage, data, &second_ticket, 1);
|
||||
|
||||
printf_ppk_data(parsed_data, &second_ticket, true);
|
||||
|
||||
furi_string_free(second_ticket.departure_name);
|
||||
furi_string_free(second_ticket.destination_name);
|
||||
furi_string_free(second_ticket.trip_start_sta_name);
|
||||
furi_string_free(second_ticket.trip_end_sta_name);
|
||||
}
|
||||
|
||||
// Print card number with 4-digit groups. "3" in "3078" denotes a ticket type "3 - full ticket", will differ on discounted cards.
|
||||
furi_string_cat_printf(parsed_data, "Number: ");
|
||||
FuriString* card_number_s = furi_string_alloc();
|
||||
furi_string_cat_printf(card_number_s, "%lld", card_number);
|
||||
FuriString* tmp_s = furi_string_alloc_set_str("9643 3078 ");
|
||||
for(uint8_t i = 0; i < 24; i += 4) {
|
||||
for(uint8_t j = 0; j < 4; j++) {
|
||||
furi_string_push_back(tmp_s, furi_string_get_char(card_number_s, i + j));
|
||||
}
|
||||
furi_string_push_back(tmp_s, ' ');
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tmp_s));
|
||||
// this works for 2K Plantain
|
||||
if(data->type == MfClassicType1k) {
|
||||
//balance
|
||||
uint32_t balance = 0;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
|
||||
|
||||
//trips
|
||||
uint8_t trips_metro = data->block[21].data[0];
|
||||
uint8_t trips_ground = data->block[21].data[1];
|
||||
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||
//trip time
|
||||
uint32_t last_trip_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
DateTime last_trip = {0};
|
||||
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Trip start: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_trip.day,
|
||||
last_trip.month,
|
||||
last_trip.year,
|
||||
last_trip.hour,
|
||||
last_trip.minute);
|
||||
//validator
|
||||
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
|
||||
//tariff
|
||||
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
|
||||
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
|
||||
//trips in metro
|
||||
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||
//trips on ground
|
||||
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||
//last payment
|
||||
uint32_t last_payment_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_payment_timestamp = (last_payment_timestamp << 8) |
|
||||
data->block[18].data[4 - i];
|
||||
}
|
||||
DateTime last_payment_date = {0};
|
||||
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Last pay: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_payment_date.day,
|
||||
last_payment_date.month,
|
||||
last_payment_date.year,
|
||||
last_payment_date.hour,
|
||||
last_payment_date.minute);
|
||||
//Last payment amount.
|
||||
uint16_t last_payment = ((data->block[18].data[10] << 16) |
|
||||
(data->block[18].data[9] << 8) | (data->block[18].data[8])) /
|
||||
100;
|
||||
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment);
|
||||
furi_string_free(card_number_s);
|
||||
furi_string_free(tmp_s);
|
||||
//This is for 4K Plantains.
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
//balance
|
||||
uint32_t balance = 0;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
balance = (balance << 8) | data->block[16].data[3 - i];
|
||||
}
|
||||
furi_string_cat_printf(parsed_data, "Balance: %ld rub\n", balance / 100);
|
||||
|
||||
//trips
|
||||
uint8_t trips_metro = data->block[21].data[0];
|
||||
uint8_t trips_ground = data->block[21].data[1];
|
||||
furi_string_cat_printf(parsed_data, "Trips: %d\n", trips_metro + trips_ground);
|
||||
//trip time
|
||||
uint32_t last_trip_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_trip_timestamp = (last_trip_timestamp << 8) | data->block[21].data[4 - i];
|
||||
}
|
||||
DateTime last_trip = {0};
|
||||
from_minutes_to_datetime(last_trip_timestamp + 24 * 60, &last_trip, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Trip start: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_trip.day,
|
||||
last_trip.month,
|
||||
last_trip.year,
|
||||
last_trip.hour,
|
||||
last_trip.minute);
|
||||
//validator
|
||||
uint16_t validator = (data->block[20].data[5] << 8) | data->block[20].data[4];
|
||||
furi_string_cat_printf(parsed_data, "Validator: %d\n", validator);
|
||||
//tariff
|
||||
uint16_t fare = (data->block[20].data[7] << 8) | data->block[20].data[6];
|
||||
furi_string_cat_printf(parsed_data, "Tariff: %d rub\n", fare / 100);
|
||||
//trips in metro
|
||||
furi_string_cat_printf(parsed_data, "Trips (Metro): %d\n", trips_metro);
|
||||
//trips on ground
|
||||
furi_string_cat_printf(parsed_data, "Trips (Ground): %d\n", trips_ground);
|
||||
//last payment
|
||||
uint32_t last_payment_timestamp = 0;
|
||||
for(uint8_t i = 0; i < 3; i++) {
|
||||
last_payment_timestamp = (last_payment_timestamp << 8) |
|
||||
data->block[18].data[4 - i];
|
||||
}
|
||||
DateTime last_payment_date = {0};
|
||||
from_minutes_to_datetime(last_payment_timestamp + 24 * 60, &last_payment_date, 2010);
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Last pay: %02d.%02d.%04d %02d:%02d\n",
|
||||
last_payment_date.day,
|
||||
last_payment_date.month,
|
||||
last_payment_date.year,
|
||||
last_payment_date.hour,
|
||||
last_payment_date.minute);
|
||||
//Last payment amount
|
||||
uint16_t last_payment = ((data->block[18].data[10] << 16) |
|
||||
(data->block[18].data[9] << 8) | (data->block[18].data[8])) /
|
||||
100;
|
||||
furi_string_cat_printf(parsed_data, "Amount: %d rub", last_payment);
|
||||
furi_string_free(card_number_s);
|
||||
furi_string_free(tmp_s);
|
||||
}
|
||||
parsed = true;
|
||||
} while(false);
|
||||
furi_string_free(ticket.departure_name);
|
||||
furi_string_free(ticket.destination_name);
|
||||
furi_string_free(ticket.trip_start_sta_name);
|
||||
furi_string_free(ticket.trip_end_sta_name);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL (<me@willyjl.dev>) for help!
|
||||
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#define PPK_WHOLE_EPOCH_START 946684800 //2000-01-01
|
||||
#define PPK_CURRENT_EPOCH_START 1388534400 //2014-01-01
|
||||
#define SECONDS_IN_A_DAY 86400
|
||||
#define SECONDS_IN_A_MINUTE 60
|
||||
#define FIRST_PPK_TICKET_OFFSET 76
|
||||
#define FIRST_TICKET_VALUE_BLOCK 77
|
||||
#define SECOND_PPK_TICKET_OFFSET 88
|
||||
#define SECOND_TICKET_VALUE_BLOCK 89
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
typedef struct {
|
||||
uint16_t departure_uic;
|
||||
uint16_t destination_uic;
|
||||
uint16_t trip_start_uic;
|
||||
uint16_t trip_end_uic;
|
||||
FuriString* departure_name;
|
||||
FuriString* destination_name;
|
||||
FuriString* trip_start_sta_name;
|
||||
FuriString* trip_end_sta_name;
|
||||
uint8_t value_data;
|
||||
uint8_t current_status;
|
||||
uint16_t valid_from_data;
|
||||
uint16_t valid_till_data;
|
||||
DateTime valid_from;
|
||||
DateTime valid_till;
|
||||
uint32_t tap_data;
|
||||
DateTime tap_time;
|
||||
uint8_t first_ticket_marker;
|
||||
uint8_t second_ticket_marker;
|
||||
uint8_t ppk_cnt;
|
||||
} TicketData;
|
||||
|
||||
static const MfClassicKeyPair t_card_2k[] = {
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //0
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //1
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //2
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //3
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //4
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //5
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //6
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //7
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //8
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //9
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //10
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //11
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //12
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //13
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //14
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //15
|
||||
|
||||
{.a = 0x061029A4A6A4, .b = 0xCF43E52FC23D}, //16
|
||||
{.a = 0xE09F2EC8229C, .b = 0x044D78B70F92}, //17
|
||||
{.a = 0x66843B4FF86E, .b = 0x4474EB131577}, //18
|
||||
{.a = 0x9AC9A9B5E809, .b = 0x558B8E45E568}, //19
|
||||
{.a = 0xC114DE1AD90F, .b = 0xA0A9033E2A09}, //20
|
||||
{.a = 0xF696EFFDD838, .b = 0xA38333EC3F72}, //21
|
||||
{.a = 0xAB50BDE9CD04, .b = 0x67991D678AED}, //22
|
||||
{.a = 0x9041DD7B236C, .b = 0x8A527C8CA237}, //23
|
||||
{.a = 0x7D40A5AA63D0, .b = 0x4A61C563DD8A}, //24
|
||||
{.a = 0x05D0520EC52B, .b = 0xB40BD14E6CB4}, //25
|
||||
{.a = 0x9354F1BFF80F, .b = 0xF99D40CBBA63}, //26
|
||||
{.a = 0x84132D9FD1AF, .b = 0x1BAA2563C14D}, //27
|
||||
{.a = 0x27A8E6436B01, .b = 0x4DB883715A5C}, //28
|
||||
{.a = 0x58B173568D26, .b = 0x2E4ADD66E35E}, //29
|
||||
{.a = 0x1BE71601E73C, .b = 0xB855F30C54FB}, //30
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF}, //31
|
||||
|
||||
};
|
||||
|
||||
static inline void sz_uic_to_sta(Storage* storage, const char* file_name, TicketData* ticket) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* departure_uic = furi_string_alloc_printf("%04X", ticket->departure_uic);
|
||||
FuriString* destination_uic = furi_string_alloc_printf("%04X", ticket->destination_uic);
|
||||
FuriString* trip_start_uic = furi_string_alloc_printf("%04X", ticket->trip_start_uic);
|
||||
FuriString* trip_end_uic = furi_string_alloc_printf("%04X", ticket->trip_end_uic);
|
||||
|
||||
if(flipper_format_file_open_existing(file, file_name)) {
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(departure_uic), ticket->departure_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(destination_uic), ticket->destination_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_start_uic), ticket->trip_start_sta_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_end_uic), ticket->trip_end_sta_name);
|
||||
}
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_string_free(departure_uic);
|
||||
furi_string_free(destination_uic);
|
||||
furi_string_free(trip_start_uic);
|
||||
furi_string_free(trip_end_uic);
|
||||
}
|
||||
|
||||
static void resolve_station_name(Storage* storage, TicketData* ticket) {
|
||||
sz_uic_to_sta(storage, EXT_PATH("nfc/assets/sev_id.nfc"), ticket);
|
||||
if(furi_string_utf8_length(ticket->departure_name) <= 2) {
|
||||
furi_string_printf(ticket->departure_name, "1f%04X", ticket->departure_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->destination_name) <= 2) {
|
||||
furi_string_printf(ticket->destination_name, "1F%04X", ticket->destination_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_start_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_start_sta_name, "1F%04X", ticket->trip_start_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_end_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_end_sta_name, "1F%04X", ticket->trip_end_uic);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void extract_ppk_data(
|
||||
Storage* storage,
|
||||
const MfClassicData* data,
|
||||
TicketData* ticket,
|
||||
bool ticket_number) {
|
||||
if(ticket_number == 0) {
|
||||
ticket->departure_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[FIRST_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
|
||||
} else {
|
||||
ticket->departure_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[SECOND_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
}
|
||||
ticket->first_ticket_marker = data->block[FIRST_PPK_TICKET_OFFSET].data[0];
|
||||
ticket->second_ticket_marker = data->block[SECOND_PPK_TICKET_OFFSET].data[0];
|
||||
const uint32_t valid_from_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_from_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t valid_till_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_till_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t tap_timestamp =
|
||||
PPK_CURRENT_EPOCH_START + ticket->tap_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(valid_from_timestamp, &ticket->valid_from);
|
||||
datetime_timestamp_to_datetime(valid_till_timestamp, &ticket->valid_till);
|
||||
datetime_timestamp_to_datetime(tap_timestamp, &ticket->tap_time);
|
||||
resolve_station_name(storage, ticket);
|
||||
}
|
||||
|
||||
static void
|
||||
printf_transport_card(FuriString* parsed_data, TicketData* ticket, bool ticket_number) {
|
||||
if(ticket->departure_uic == 0x0000) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Unknown SevPPK Card\n NO TICKET DATA FOUND \n\nTHE TICKET IS NOT ISSUED\nOR LAYOUT IS UNKNOWN\n");
|
||||
return;
|
||||
} else {
|
||||
if(ticket_number == 0) {
|
||||
furi_string_cat_printf(parsed_data, "\e#SevPPK Transport Card\n");
|
||||
switch(ticket->first_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X", ticket->first_ticket_marker);
|
||||
}
|
||||
} else {
|
||||
furi_string_cat_printf(parsed_data, "\e#Second Ticket:");
|
||||
switch(ticket->second_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X", ticket->second_ticket_marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nFrom:>%s\nTo:>%s\nValid From: %02d-%02d-%04d\nValid thru: %02d-%02d-%04d\n",
|
||||
furi_string_get_cstr(ticket->departure_name),
|
||||
furi_string_get_cstr(ticket->destination_name),
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year,
|
||||
ticket->valid_till.day,
|
||||
ticket->valid_till.month,
|
||||
ticket->valid_till.year);
|
||||
|
||||
if(ticket->value_data > 0) {
|
||||
furi_string_cat_printf(parsed_data, "Rides remain: %02d\n", ticket->value_data);
|
||||
}
|
||||
|
||||
switch(ticket->current_status) {
|
||||
case 0x00:
|
||||
furi_string_cat_printf(parsed_data, "Status:> NOT USED\n");
|
||||
break;
|
||||
case 0x80:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> ENTERED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_start_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x1F:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> EXITED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_end_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> UNKNOWN (%02X)\nPPK CNT: %03d",
|
||||
ticket->current_status,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool sev_tk_verify(Nfc* nfc) {
|
||||
const uint8_t verify_sector = 19;
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
|
||||
MfClassicKey key = {};
|
||||
bit_lib_num_to_bytes_be(t_card_2k[verify_sector].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
return error == MfClassicErrorNone;
|
||||
}
|
||||
|
||||
static bool sev_tk_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
bool is_read = false;
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
|
||||
for(size_t i = 0; i < 32; i++) {
|
||||
bit_lib_num_to_bytes_be(t_card_2k[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(t_card_2k[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNotPresent) {
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
}
|
||||
}
|
||||
|
||||
mf_classic_free(data);
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool sev_tk_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 19);
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != t_card_2k[19].a) break;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
TicketData primary_ticket = {0};
|
||||
primary_ticket.departure_name = furi_string_alloc();
|
||||
primary_ticket.destination_name = furi_string_alloc();
|
||||
primary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
primary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
extract_ppk_data(storage, data, &primary_ticket, 0);
|
||||
|
||||
printf_transport_card(parsed_data, &primary_ticket, false);
|
||||
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
|
||||
if(primary_ticket.second_ticket_marker != 0) {
|
||||
TicketData secondary_ticket = {0};
|
||||
secondary_ticket.departure_name = furi_string_alloc();
|
||||
secondary_ticket.destination_name = furi_string_alloc();
|
||||
secondary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
secondary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
|
||||
extract_ppk_data(storage, data, &secondary_ticket, 1);
|
||||
printf_transport_card(parsed_data, &secondary_ticket, true);
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static const NfcSupportedCardsPlugin sev_tk_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = sev_tk_verify,
|
||||
.read = sev_tk_read,
|
||||
.parse = sev_tk_parse,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor sev_tk_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &sev_tk_plugin,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* sev_tk_plugin_ep(void) {
|
||||
return &sev_tk_plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL (<me@willyjl.dev>) for help!
|
||||
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#define PPK_WHOLE_EPOCH_START 946684800 //2000-01-01
|
||||
#define PPK_CURRENT_EPOCH_START 1388534400 //2014-01-01
|
||||
#define SECONDS_IN_A_DAY 86400
|
||||
#define SECONDS_IN_A_MINUTE 60
|
||||
#define FIRST_PPK_TICKET_OFFSET 76
|
||||
#define FIRST_TICKET_VALUE_BLOCK 77
|
||||
#define SECOND_PPK_TICKET_OFFSET 88
|
||||
#define SECOND_TICKET_VALUE_BLOCK 89
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
typedef struct {
|
||||
uint16_t departure_uic;
|
||||
uint16_t destination_uic;
|
||||
uint16_t trip_start_uic;
|
||||
uint16_t trip_end_uic;
|
||||
FuriString* departure_name;
|
||||
FuriString* destination_name;
|
||||
FuriString* trip_start_sta_name;
|
||||
FuriString* trip_end_sta_name;
|
||||
uint8_t value_data;
|
||||
uint8_t current_status;
|
||||
uint16_t valid_from_data;
|
||||
uint16_t valid_till_data;
|
||||
DateTime valid_from;
|
||||
DateTime valid_till;
|
||||
uint32_t tap_data;
|
||||
DateTime tap_time;
|
||||
uint8_t first_ticket_marker;
|
||||
uint8_t second_ticket_marker;
|
||||
uint8_t ppk_cnt;
|
||||
} TicketData;
|
||||
|
||||
static const MfClassicKeyPair t_card_4k[] = {
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B4B2B1B3B6}, //0
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B5B2B4B9B0}, //1
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B8B4B6B1B3}, //2
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B3B8B5B7}, //3
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B6B4B2B8}, //4
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B4B5B3B8}, //5
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B8B1B7B8}, //6
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B5B1B8B1}, //7
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B6B4B8B7}, //8
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B9B2B9B2B4}, //9
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B1B8B0B6}, //10
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B7B8B9B5B5}, //11
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B7B1B0B0}, //12
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B9B2B8B3B2}, //13
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B4B1B0B4B6}, //14
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B4B2B5B4B8}, //15
|
||||
{.a = 0x684CE8377AB8, .b = 0x91888D728D9E}, //16
|
||||
{.a = 0xF462ED255B44, .b = 0x0D40620AC610}, //17
|
||||
{.a = 0xF1E8FD5B5C9F, .b = 0xABB5763550B0}, //18
|
||||
{.a = 0xE9E3265B45B1, .b = 0x11D848B26034}, //19
|
||||
{.a = 0xAEA3755DFA82, .b = 0x36D1BAC1395E}, //20
|
||||
{.a = 0x83F58B205854, .b = 0x967DAFCF2674}, //21
|
||||
{.a = 0x5CDF18E68A75, .b = 0x1689C175B14E}, //22
|
||||
{.a = 0x126DC25C5D53, .b = 0x346B03AF1FF3}, //23
|
||||
{.a = 0xF1B013C4495C, .b = 0x74CE14DBC71F}, //24
|
||||
{.a = 0xA5FE4FAD0269, .b = 0x0025FEA845E5}, //25
|
||||
{.a = 0x43080428049C, .b = 0x2E91BB6F511E}, //26
|
||||
{.a = 0x4F44A08C51BC, .b = 0xE44CC58DF833}, //27
|
||||
{.a = 0x3FEC92F652BE, .b = 0x942039006B83}, //28
|
||||
{.a = 0x3A3BE5B635FA, .b = 0xFC564425A9BA}, //29
|
||||
{.a = 0x78C9E1C688BB, .b = 0xA29E362B22F3}, //30
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //31
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //32
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //33
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //34
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //35
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //36
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //37
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //38
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //39
|
||||
};
|
||||
|
||||
static inline void sz_uic_to_sta(Storage* storage, const char* file_name, TicketData* ticket) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* departure_uic = furi_string_alloc_printf("%04X", ticket->departure_uic);
|
||||
FuriString* destination_uic = furi_string_alloc_printf("%04X", ticket->destination_uic);
|
||||
FuriString* trip_start_uic = furi_string_alloc_printf("%04X", ticket->trip_start_uic);
|
||||
FuriString* trip_end_uic = furi_string_alloc_printf("%04X", ticket->trip_end_uic);
|
||||
|
||||
if(flipper_format_file_open_existing(file, file_name)) {
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(departure_uic), ticket->departure_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(destination_uic), ticket->destination_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_start_uic), ticket->trip_start_sta_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_end_uic), ticket->trip_end_sta_name);
|
||||
}
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_string_free(departure_uic);
|
||||
furi_string_free(destination_uic);
|
||||
furi_string_free(trip_start_uic);
|
||||
furi_string_free(trip_end_uic);
|
||||
}
|
||||
|
||||
static void resolve_station_name(Storage* storage, TicketData* ticket) {
|
||||
sz_uic_to_sta(storage, EXT_PATH("nfc/assets/sk_id.nfc"), ticket);
|
||||
if(furi_string_utf8_length(ticket->departure_name) <= 2) {
|
||||
furi_string_printf(ticket->departure_name, "1f%04X", ticket->departure_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->destination_name) <= 2) {
|
||||
furi_string_printf(ticket->destination_name, "1F%04X", ticket->destination_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_start_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_start_sta_name, "1F%04X", ticket->trip_start_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_end_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_end_sta_name, "1F%04X", ticket->trip_end_uic);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void extract_ppk_data(
|
||||
Storage* storage,
|
||||
const MfClassicData* data,
|
||||
TicketData* ticket,
|
||||
bool ticket_number) {
|
||||
if(ticket_number == 0) {
|
||||
ticket->departure_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[FIRST_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
|
||||
} else {
|
||||
ticket->departure_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[SECOND_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
}
|
||||
ticket->first_ticket_marker = data->block[FIRST_PPK_TICKET_OFFSET].data[0];
|
||||
ticket->second_ticket_marker = data->block[SECOND_PPK_TICKET_OFFSET].data[0];
|
||||
|
||||
const uint32_t valid_from_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_from_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t valid_till_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_till_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t tap_timestamp =
|
||||
PPK_CURRENT_EPOCH_START + ticket->tap_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(valid_from_timestamp, &ticket->valid_from);
|
||||
datetime_timestamp_to_datetime(valid_till_timestamp, &ticket->valid_till);
|
||||
datetime_timestamp_to_datetime(tap_timestamp, &ticket->tap_time);
|
||||
resolve_station_name(storage, ticket);
|
||||
}
|
||||
|
||||
static void
|
||||
printf_transport_card(FuriString* parsed_data, TicketData* ticket, bool ticket_number) {
|
||||
if(ticket->departure_uic == 0x0000) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Unknown SKPPK Card\n NO TICKET DATA FOUND \n\nTHE TICKET IS NOT ISSUED\nOR LAYOUT IS UNKNOWN\n");
|
||||
return;
|
||||
} else {
|
||||
if(ticket_number == 0) {
|
||||
furi_string_cat_printf(parsed_data, "\e#SKPPK Transport Card\n");
|
||||
switch(ticket->first_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides.");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X\n", ticket->first_ticket_marker);
|
||||
}
|
||||
} else {
|
||||
furi_string_cat_printf(parsed_data, "\e#Second Ticket:\n");
|
||||
switch(ticket->second_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides.");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X", ticket->second_ticket_marker);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nFrom:>%s\nTo:>%s\nValid From: %02d-%02d-%04d\nValid thru: %02d-%02d-%04d\n",
|
||||
furi_string_get_cstr(ticket->departure_name),
|
||||
furi_string_get_cstr(ticket->destination_name),
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year,
|
||||
ticket->valid_till.day,
|
||||
ticket->valid_till.month,
|
||||
ticket->valid_till.year);
|
||||
|
||||
if(ticket->value_data > 0) {
|
||||
furi_string_cat_printf(parsed_data, "Rides remain: %02d\n", ticket->value_data);
|
||||
}
|
||||
|
||||
switch(ticket->current_status) {
|
||||
case 0x00:
|
||||
furi_string_cat_printf(parsed_data, "Status:> NOT USED\n");
|
||||
break;
|
||||
case 0x80:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> ENTERED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_start_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x1F:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> EXITED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_end_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> UNKNOWN (%02X)\nPPK CNT: %03d",
|
||||
ticket->current_status,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool sk_tk_verify(Nfc* nfc) {
|
||||
const uint8_t verify_sector = 19;
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
|
||||
MfClassicKey key = {};
|
||||
bit_lib_num_to_bytes_be(t_card_4k[verify_sector].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
return error == MfClassicErrorNone;
|
||||
}
|
||||
|
||||
static bool sk_tk_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
bool is_read = false;
|
||||
MfClassicType type = MfClassicType4k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
|
||||
for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
|
||||
bit_lib_num_to_bytes_be(t_card_4k[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(t_card_4k[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNotPresent) {
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
}
|
||||
}
|
||||
|
||||
mf_classic_free(data);
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool sk_tk_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 19);
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != t_card_4k[19].a) break;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
TicketData primary_ticket = {0};
|
||||
primary_ticket.departure_name = furi_string_alloc();
|
||||
primary_ticket.destination_name = furi_string_alloc();
|
||||
primary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
primary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
extract_ppk_data(storage, data, &primary_ticket, 0);
|
||||
|
||||
printf_transport_card(parsed_data, &primary_ticket, false);
|
||||
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
|
||||
if(primary_ticket.second_ticket_marker != 0) {
|
||||
TicketData secondary_ticket = {0};
|
||||
secondary_ticket.departure_name = furi_string_alloc();
|
||||
secondary_ticket.destination_name = furi_string_alloc();
|
||||
secondary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
secondary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
|
||||
extract_ppk_data(storage, data, &secondary_ticket, 1);
|
||||
printf_transport_card(parsed_data, &secondary_ticket, true);
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
parsed = true;
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static const NfcSupportedCardsPlugin sk_tk_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = sk_tk_verify,
|
||||
.read = sk_tk_read,
|
||||
.parse = sk_tk_parse,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor sk_tk_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &sk_tk_plugin,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* sk_tk_plugin_ep(void) {
|
||||
return &sk_tk_plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL (<me@willyjl.dev>) for help!
|
||||
|
||||
#include "nfc_supported_card_plugin.h"
|
||||
#include <flipper_application/flipper_application.h>
|
||||
#include <nfc/nfc_device.h>
|
||||
#include <bit_lib/bit_lib.h>
|
||||
#include <datetime.h>
|
||||
#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#define PPK_WHOLE_EPOCH_START 946684800 //2000-01-01
|
||||
#define PPK_CURRENT_EPOCH_START 1388534400 //1388530800 //2014-01-01
|
||||
#define SECONDS_IN_A_DAY 86400
|
||||
#define SECONDS_IN_A_MINUTE 60
|
||||
#define FIRST_PPK_TICKET_OFFSET 76
|
||||
#define FIRST_TICKET_VALUE_BLOCK 77
|
||||
#define SECOND_PPK_TICKET_OFFSET 88
|
||||
#define SECOND_TICKET_VALUE_BLOCK 89
|
||||
|
||||
typedef struct {
|
||||
uint64_t a;
|
||||
uint64_t b;
|
||||
} MfClassicKeyPair;
|
||||
|
||||
typedef struct {
|
||||
uint16_t departure_uic;
|
||||
uint16_t destination_uic;
|
||||
uint16_t trip_start_uic;
|
||||
uint16_t trip_end_uic;
|
||||
FuriString* departure_name;
|
||||
FuriString* destination_name;
|
||||
FuriString* trip_start_sta_name;
|
||||
FuriString* trip_end_sta_name;
|
||||
uint8_t value_data;
|
||||
uint8_t current_status;
|
||||
uint16_t valid_from_data;
|
||||
uint16_t valid_till_data;
|
||||
DateTime valid_from;
|
||||
DateTime valid_till;
|
||||
uint32_t tap_data;
|
||||
DateTime tap_time;
|
||||
uint8_t first_ticket_marker;
|
||||
uint8_t second_ticket_marker;
|
||||
uint8_t ppk_cnt;
|
||||
} TicketData;
|
||||
|
||||
static const MfClassicKeyPair so_card_2k[] = {
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B7B5B8B4B5}, //0
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B6B2B9B8B7}, //1
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B2B4B4B4B0}, //2
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B3B5B2B2B2}, //3
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B8B4B8B0B9}, //4
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B3B7B9B8B6}, //5
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B6B5B5B5B6}, //6
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B7B0B0B2B0}, //7
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B1B2B8B0}, //8
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B1B8B3B2}, //9
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B0B0B3B1B8}, //10
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B2B6B7B5B2}, //11
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B9B2B0B2B0}, //12
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B7B8B8B7B3}, //13
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B3B4B9B2B0}, //14
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B7B2B0B0B9}, //15
|
||||
{.a = 0xA94CAB611187, .b = 0x389109BD1D82}, //16
|
||||
{.a = 0xBF4280329F11, .b = 0x28D9EDD2096D}, //17
|
||||
{.a = 0xDE6BD90BD6B0, .b = 0x94866C16E9A4}, //18
|
||||
{.a = 0x2EA9493CAA7C, .b = 0x5068BCE2BC1C}, //19
|
||||
{.a = 0x15A41BA53F6C, .b = 0x3BD3CF43571C}, //20
|
||||
{.a = 0x1290FFD80DB5, .b = 0xD821B7916B7E}, //21
|
||||
{.a = 0x68C1A07E96A9, .b = 0x2B3323E75750}, //22
|
||||
{.a = 0xC699831AB307, .b = 0xCD7F7E9111F1}, //23
|
||||
{.a = 0x4E5884BF23E9, .b = 0x2287812A6AEE}, //24
|
||||
{.a = 0xC55212F716DC, .b = 0x594E368CCEFF}, //25
|
||||
{.a = 0x6EF127E674B1, .b = 0xDD21C8D3E0B9}, //26
|
||||
{.a = 0xFB79FAF4B55C, .b = 0xFE52B3B2A93B}, //27
|
||||
{.a = 0x6CF85CDFF647, .b = 0xCCAD7C41FC8A}, //28
|
||||
{.a = 0x591F6C130F91, .b = 0x2D2B734ECF91}, //29
|
||||
{.a = 0xEEB83529B79B, .b = 0xCB14E70EBA38}, //30
|
||||
{.a = 0xFFFFFFFFFFFF, .b = 0xB0B1B2B3B4B5}, //31
|
||||
};
|
||||
|
||||
static inline void sz_uic_to_sta(Storage* storage, const char* file_name, TicketData* ticket) {
|
||||
FlipperFormat* file = flipper_format_file_alloc(storage);
|
||||
FuriString* departure_uic = furi_string_alloc_printf("%04X", ticket->departure_uic);
|
||||
FuriString* destination_uic = furi_string_alloc_printf("%04X", ticket->destination_uic);
|
||||
FuriString* trip_start_uic = furi_string_alloc_printf("%04X", ticket->trip_start_uic);
|
||||
FuriString* trip_end_uic = furi_string_alloc_printf("%04X", ticket->trip_end_uic);
|
||||
|
||||
if(flipper_format_file_open_existing(file, file_name)) {
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(departure_uic), ticket->departure_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(destination_uic), ticket->destination_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_start_uic), ticket->trip_start_sta_name);
|
||||
flipper_format_rewind(file);
|
||||
flipper_format_read_string(
|
||||
file, furi_string_get_cstr(trip_end_uic), ticket->trip_end_sta_name);
|
||||
}
|
||||
|
||||
flipper_format_free(file);
|
||||
furi_string_free(departure_uic);
|
||||
furi_string_free(destination_uic);
|
||||
furi_string_free(trip_start_uic);
|
||||
furi_string_free(trip_end_uic);
|
||||
}
|
||||
// Function to resolve station names for a ticket, and if not found, set to "1E" + UIC code
|
||||
static void resolve_station_name(Storage* storage, TicketData* ticket) {
|
||||
sz_uic_to_sta(storage, EXT_PATH("nfc/assets/sz_id.nfc"), ticket);
|
||||
if(furi_string_utf8_length(ticket->departure_name) <= 2) {
|
||||
furi_string_printf(ticket->departure_name, "1E%04X", ticket->departure_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->destination_name) <= 2) {
|
||||
furi_string_printf(ticket->destination_name, "1E%04X", ticket->destination_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_start_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_start_sta_name, "1E%04X", ticket->trip_start_uic);
|
||||
}
|
||||
if(furi_string_utf8_length(ticket->trip_end_sta_name) <= 2) {
|
||||
furi_string_printf(ticket->trip_end_sta_name, "1E%04X", ticket->trip_end_uic);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void extract_ppk_data(
|
||||
Storage* storage,
|
||||
const MfClassicData* data,
|
||||
TicketData* ticket,
|
||||
bool ticket_number) {
|
||||
if(ticket_number == 0) {
|
||||
ticket->departure_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[FIRST_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[FIRST_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[FIRST_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[FIRST_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[FIRST_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
|
||||
} else {
|
||||
ticket->departure_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[6] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[5]);
|
||||
ticket->destination_uic = (data->block[SECOND_PPK_TICKET_OFFSET].data[9] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[8]);
|
||||
ticket->value_data = data->block[SECOND_TICKET_VALUE_BLOCK].data[0];
|
||||
ticket->current_status = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[8];
|
||||
ticket->valid_from_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[2] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[1]);
|
||||
ticket->valid_till_data = (data->block[SECOND_PPK_TICKET_OFFSET].data[4] << 8) |
|
||||
(data->block[SECOND_PPK_TICKET_OFFSET].data[3]);
|
||||
ticket->tap_data = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[2] << 16) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[1] << 8) |
|
||||
data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[0];
|
||||
ticket->ppk_cnt = data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[10];
|
||||
ticket->trip_start_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[4] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[3]);
|
||||
ticket->trip_end_uic = (data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[7] << 8) |
|
||||
(data->block[SECOND_TICKET_VALUE_BLOCK + 1].data[6]);
|
||||
}
|
||||
ticket->first_ticket_marker = data->block[FIRST_PPK_TICKET_OFFSET].data[0];
|
||||
ticket->second_ticket_marker = data->block[SECOND_PPK_TICKET_OFFSET].data[0];
|
||||
const uint32_t valid_from_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_from_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t valid_till_timestamp =
|
||||
PPK_WHOLE_EPOCH_START + ticket->valid_till_data * SECONDS_IN_A_DAY;
|
||||
const uint32_t tap_timestamp =
|
||||
PPK_CURRENT_EPOCH_START + ticket->tap_data * SECONDS_IN_A_MINUTE;
|
||||
datetime_timestamp_to_datetime(valid_from_timestamp, &ticket->valid_from);
|
||||
datetime_timestamp_to_datetime(valid_till_timestamp, &ticket->valid_till);
|
||||
datetime_timestamp_to_datetime(tap_timestamp, &ticket->tap_time);
|
||||
resolve_station_name(storage, ticket);
|
||||
}
|
||||
|
||||
static inline bool is_accompany_card(uint16_t departure_uic, uint16_t destination_uic) {
|
||||
return departure_uic == destination_uic;
|
||||
}
|
||||
|
||||
static void printf_accompany_card(TicketData* ticket, FuriString* parsed_data) {
|
||||
if(ticket->departure_uic == 0x0000) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Unknown SZPPK Card\n NO TICKET DATA FOUND \n\nTHE TICKET IS NOT ISSUED\nOR LAYOUT IS UNKNOWN\n");
|
||||
return;
|
||||
} else {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#SZPPK Accompany Card\nValid on: %02d-%02d-%04d\nStation: > %s\n",
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year,
|
||||
furi_string_get_cstr(ticket->departure_name));
|
||||
|
||||
switch(ticket->current_status) {
|
||||
case 0x00:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Status:> NOT USED\nPPK CNT: %03d\n", ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x80:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> ENTERED STATION\nChecked in at:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x1E:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> EXITED STATION\nChecked out at:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Status:> UNKNOWN\nPPK CNT: %03d\n", ticket->ppk_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
printf_transport_card(FuriString* parsed_data, TicketData* ticket, bool ticket_number) {
|
||||
if(ticket->departure_uic == 0x0000) {
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\e#Unknown SZPPK Card\n NO TICKET DATA FOUND \n\nTHE TICKET IS NOT ISSUED\nOR LAYOUT IS UNKNOWN\n");
|
||||
return;
|
||||
} else {
|
||||
if(ticket_number == 0) {
|
||||
furi_string_cat_printf(parsed_data, "\e#SZPPK Transport Card\n");
|
||||
switch(ticket->first_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides.");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X\n", ticket->first_ticket_marker);
|
||||
}
|
||||
} else {
|
||||
furi_string_cat_printf(parsed_data, "\e#Second Ticket:\n");
|
||||
switch(ticket->second_ticket_marker) {
|
||||
case 0x02:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 5 days unlim.");
|
||||
break;
|
||||
case 0x06:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 10 rides");
|
||||
break;
|
||||
case 0x18:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 30 days unlim.");
|
||||
break;
|
||||
case 0x1B:
|
||||
furi_string_cat_printf(parsed_data, "Type:> 20 rides.");
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Type: Unknown, 0x%02X", ticket->second_ticket_marker);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nFrom:>%s\nTo:>%s\nValid From: %02d-%02d-%04d\nValid thru: %02d-%02d-%04d",
|
||||
furi_string_get_cstr(ticket->departure_name),
|
||||
furi_string_get_cstr(ticket->destination_name),
|
||||
ticket->valid_from.day,
|
||||
ticket->valid_from.month,
|
||||
ticket->valid_from.year,
|
||||
ticket->valid_till.day,
|
||||
ticket->valid_till.month,
|
||||
ticket->valid_till.year);
|
||||
|
||||
if(ticket->value_data > 0) {
|
||||
furi_string_cat_printf(parsed_data, "Rides remain: %02d\n", ticket->value_data);
|
||||
}
|
||||
|
||||
switch(ticket->current_status) {
|
||||
case 0x00:
|
||||
furi_string_cat_printf(
|
||||
parsed_data, "Status:> NOT USED\nPPK CNT: %03d\n", ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x80:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nStatus:> ENTERED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_start_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
case 0x1E:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"\nStatus:> EXITED STATION\nSta name:> %s\nLast pass on:> %02d-%02d-%04d\nPass time:> %02d:%02d\nPPK CNT: %03d\n",
|
||||
furi_string_get_cstr(ticket->trip_end_sta_name),
|
||||
ticket->tap_time.day,
|
||||
ticket->tap_time.month,
|
||||
ticket->tap_time.year,
|
||||
ticket->tap_time.hour,
|
||||
ticket->tap_time.minute,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
default:
|
||||
furi_string_cat_printf(
|
||||
parsed_data,
|
||||
"Status:> UNKNOWN (%02X)\nPPK CNT: %03d",
|
||||
ticket->current_status,
|
||||
ticket->ppk_cnt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool szppk_so_verify(Nfc* nfc) {
|
||||
const uint8_t verify_sector = 19;
|
||||
const uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
|
||||
MfClassicKey key = {};
|
||||
bit_lib_num_to_bytes_be(so_card_2k[verify_sector].a, COUNT_OF(key.data), key.data);
|
||||
|
||||
MfClassicAuthContext auth_ctx = {};
|
||||
MfClassicError error =
|
||||
mf_classic_poller_sync_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
|
||||
|
||||
return error == MfClassicErrorNone;
|
||||
}
|
||||
|
||||
static bool szppk_so_read(Nfc* nfc, NfcDevice* device) {
|
||||
furi_assert(nfc);
|
||||
furi_assert(device);
|
||||
|
||||
MfClassicData* data = mf_classic_alloc();
|
||||
nfc_device_copy_data(device, NfcProtocolMfClassic, data);
|
||||
|
||||
bool is_read = false;
|
||||
MfClassicType type = MfClassicType1k;
|
||||
MfClassicError error = mf_classic_poller_sync_detect_type(nfc, &type);
|
||||
|
||||
if(error == MfClassicErrorNone) {
|
||||
data->type = type;
|
||||
MfClassicDeviceKeys keys = {};
|
||||
|
||||
for(size_t i = 0; i < 32; i++) {
|
||||
bit_lib_num_to_bytes_be(so_card_2k[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
|
||||
FURI_BIT_SET(keys.key_a_mask, i);
|
||||
bit_lib_num_to_bytes_be(so_card_2k[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
|
||||
FURI_BIT_SET(keys.key_b_mask, i);
|
||||
}
|
||||
|
||||
error = mf_classic_poller_sync_read(nfc, &keys, data);
|
||||
if(error != MfClassicErrorNotPresent) {
|
||||
nfc_device_set_data(device, NfcProtocolMfClassic, data);
|
||||
is_read = (error == MfClassicErrorNone);
|
||||
}
|
||||
}
|
||||
|
||||
mf_classic_free(data);
|
||||
return is_read;
|
||||
}
|
||||
|
||||
static bool szppk_so_parse(const NfcDevice* device, FuriString* parsed_data) {
|
||||
furi_assert(device);
|
||||
|
||||
const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
|
||||
bool parsed = false;
|
||||
|
||||
do {
|
||||
MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 19);
|
||||
uint64_t key = bit_lib_bytes_to_num_be(sec_tr->key_a.data, 6);
|
||||
if(key != so_card_2k[19].a) break;
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
TicketData primary_ticket = {0};
|
||||
primary_ticket.departure_name = furi_string_alloc();
|
||||
primary_ticket.destination_name = furi_string_alloc();
|
||||
primary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
primary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
extract_ppk_data(storage, data, &primary_ticket, 0);
|
||||
if(is_accompany_card(primary_ticket.departure_uic, primary_ticket.destination_uic)) {
|
||||
printf_accompany_card(&primary_ticket, parsed_data);
|
||||
} else {
|
||||
printf_transport_card(parsed_data, &primary_ticket, false);
|
||||
}
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
|
||||
if(primary_ticket.second_ticket_marker != 0) {
|
||||
TicketData secondary_ticket = {0};
|
||||
secondary_ticket.departure_name = furi_string_alloc();
|
||||
secondary_ticket.destination_name = furi_string_alloc();
|
||||
secondary_ticket.trip_start_sta_name = furi_string_alloc();
|
||||
secondary_ticket.trip_end_sta_name = furi_string_alloc();
|
||||
|
||||
extract_ppk_data(storage, data, &secondary_ticket, 1);
|
||||
printf_transport_card(parsed_data, &secondary_ticket, true);
|
||||
furi_string_free(primary_ticket.departure_name);
|
||||
furi_string_free(primary_ticket.destination_name);
|
||||
furi_string_free(primary_ticket.trip_start_sta_name);
|
||||
furi_string_free(primary_ticket.trip_end_sta_name);
|
||||
}
|
||||
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
parsed = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
static const NfcSupportedCardsPlugin szppk_so_plugin = {
|
||||
.protocol = NfcProtocolMfClassic,
|
||||
.verify = szppk_so_verify,
|
||||
.read = szppk_so_read,
|
||||
.parse = szppk_so_parse,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor szppk_so_plugin_descriptor = {
|
||||
.appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
|
||||
.ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
|
||||
.entry_point = &szppk_so_plugin,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* szppk_so_plugin_ep(void) {
|
||||
return &szppk_so_plugin_descriptor;
|
||||
}
|
||||
@@ -39,7 +39,7 @@ bool two_cities_verify(Nfc* nfc) {
|
||||
bool verified = false;
|
||||
|
||||
do {
|
||||
const uint8_t verify_sector = 4;
|
||||
const uint8_t verify_sector = 9;
|
||||
uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
|
||||
FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
|
||||
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
Filetype: Flipper NFC resources
|
||||
Version: 1
|
||||
# staID: Name
|
||||
9468: KULISHKI
|
||||
9469: KRASNOE
|
||||
968C: UGLICH
|
||||
97A7: 40 KM
|
||||
9C61: 29 KM
|
||||
AB91: YAROSLAVL-GL.
|
||||
AB92: YAROSLAVL-PASS
|
||||
AB93: PRIVOLZHYE
|
||||
AB94: FILINO
|
||||
ABD5: 300 KM
|
||||
ABFF: 4 KM
|
||||
AC4D: SKALINO
|
||||
AC4F: MARFINO
|
||||
AC50: PRECHISTOE
|
||||
AC51: MAKAROVO
|
||||
AC53: PURSHEVO
|
||||
AC54: RODIONOVO
|
||||
AC55: MASLOVO
|
||||
AC56: NEKOUZ
|
||||
AC57: SHESTIKHINO
|
||||
AC59: VOLGA
|
||||
AC5A: KOBOSTOVO
|
||||
AC5B: TIKHMENEVO
|
||||
AC5C: LOM
|
||||
AC5D: VAULOVO
|
||||
AC5E: CHEBAKOVO
|
||||
AC64: LYUTOVO
|
||||
AC65: TOSCHIKHA
|
||||
AC66: BURMAKINO
|
||||
AC68: PANTELEEVO
|
||||
AC69: PUTYATINO
|
||||
AC6A: PUCHKOVSKIY
|
||||
AC6B: UTKINO
|
||||
AC6F: KOZMODEMNSK
|
||||
AC70: KOROMYSLOVO
|
||||
AC71: SEMIBRATOVO
|
||||
AC72: ROSTOV-YARSLV.
|
||||
AC73: PETROVSK
|
||||
AC74: SIL'NITSY
|
||||
AC75: ITLAR'
|
||||
AC77: BEKLEMISHEVO
|
||||
AC78: RYAZANTSEVO
|
||||
AC79: SHUSHKOVO
|
||||
AC7A: BERENDEEVO
|
||||
AC8A: RYBINSK-PASS.
|
||||
ACA6: SEKSHA
|
||||
ACA7: LYUBIM
|
||||
ACA9: ZHAROK
|
||||
ACAA: SOT'
|
||||
ACAB: LUNKA
|
||||
ACD4: DANOLOV
|
||||
ACE9: DUNAIKA
|
||||
ACEA: LIPOVAYA GORA
|
||||
AD03: 155 KM
|
||||
AD04: 147 KM
|
||||
AD06: 187 KM
|
||||
AD07: 231 KM
|
||||
AD08: 259 KM
|
||||
AD09: 268 KM
|
||||
AD0A: 274 KM
|
||||
AD0B: DEPO
|
||||
AD0C: 310 KM
|
||||
AD0D: 322 KM
|
||||
AD0E: 331 KM
|
||||
AD0F: 343 KM
|
||||
AD10: 351 KM
|
||||
AD45: 361 KM
|
||||
AD46: 370 KM
|
||||
AD47: 378 KM
|
||||
AD48: 388 KM
|
||||
AD4A: 399 KM
|
||||
AD4B: 409 KM
|
||||
AD4C: 419 KM
|
||||
AEB1: KOCHENYATINO
|
||||
AEB3: BAGRIMOVO
|
||||
AEB4: REKA
|
||||
AEB5: SOSNOVTSY
|
||||
AEB9: DOGADTSEVO
|
||||
AEBD: KUDRYAVTSEVO
|
||||
AEBF: PROSVET
|
||||
AEC1: GAVROLOV YAM
|
||||
AEC3: LEVTSOVO
|
||||
AEC7: HOZHAEVO
|
||||
AECA: TSYBRINO
|
||||
AECB: ROKSHA
|
||||
AEF4: 69 KM
|
||||
AEF5: 56 KM
|
||||
AEF6: 323 KM
|
||||
AEFA: 287 KM
|
||||
AEFB: 296 KM
|
||||
AEFC: 305 KM
|
||||
AEFD: 310 KM
|
||||
AEFE: 318 KM
|
||||
AF0E: 265 KM
|
||||
AF0F: 285 KM
|
||||
B057: MAKAROVSKAYA
|
||||
B05A: USHAKOVO
|
||||
B05F: VAREGOVO
|
||||
B07A: 306 KM
|
||||
B07B: 296 KM
|
||||
B07C: YURINSKIY
|
||||
B07D: TOROPOVO
|
||||
B07E: PINYAGI
|
||||
B07F: CHIZHOVO
|
||||
B080: KLINTSEVO
|
||||
B081: TENINO
|
||||
B082: MOLOT
|
||||
B083: TELISCHEVO
|
||||
B084: SUHAREZH
|
||||
B086: 324 KM
|
||||
B087: POLYANKI
|
||||
B08A: DEBOLOVSKAYA
|
||||
B08B: 2 KM
|
||||
B090: RUSHA
|
||||
B0AC: KOTOROSL'
|
||||
B0D1: 403 KM
|
||||
B0D2: SHOLOSOVO
|
||||
B0D3: MURINSKOE
|
||||
B0D4: ONAN'JINO
|
||||
B0D5: L'NOZAVOD
|
||||
B0F3: 359 KM
|
||||
@@ -0,0 +1,85 @@
|
||||
Filetype: Flipper NFC resources
|
||||
Version: 1
|
||||
# staID: Name
|
||||
8377: OP 1725 KM
|
||||
8350: STEKOL'NYI
|
||||
834F: SELHOZTECHNIKA
|
||||
834D: KANGLY
|
||||
834B: OP 1861 KM
|
||||
834A: OP 1856 KM
|
||||
8349: OP 1842 KM
|
||||
8348: OP 1832 KM
|
||||
8347: OP 1806 KM
|
||||
8344: OP 44 KM
|
||||
8343: OP 129 KM
|
||||
8288: SUVOROVSKAYA
|
||||
8188: NESKUCHNYI
|
||||
8183: OP 1741 KM
|
||||
8182: KOCHUBEEVSKII
|
||||
817E: SURKUL
|
||||
817D: OP 1823 KM
|
||||
8177: TERSKII
|
||||
8176: KRASNAYA
|
||||
8175: TEPLAYA RECHKA
|
||||
8165: DVORTSOVYI
|
||||
8164: PIONER
|
||||
8161: ORBEL'YANOVO
|
||||
815F: DZHEMUHA
|
||||
815D: ETOKA
|
||||
8158: OBIL'NYI
|
||||
8157: NINY
|
||||
813C: STAVROPOL
|
||||
8104: KARMALINOVSKII
|
||||
8084: KRASN.DEREVNIA
|
||||
807E: MIHAILOVSKAIA
|
||||
7FF8: ZMEIKA
|
||||
7FF6: MASHUK
|
||||
7FF5: LERMONTOVSKII
|
||||
7FF4: NOVOPIATIGORSK
|
||||
7FF3: SKACHKI
|
||||
7FF2: ZOLOTUSHKA
|
||||
7FF1: BELYI UGOL'
|
||||
7FF0: PODKUMOK
|
||||
7FEF: MINUTKA
|
||||
7FBA: PLAKSEIKA
|
||||
7FB9: MASLOV KUT
|
||||
7FB8: ZELENOKUMSK
|
||||
7FB7: KUMA
|
||||
7FB6: GEORGIEVSK
|
||||
7FA6: VIAZNIKI
|
||||
7FA4: YAGODKA
|
||||
7FA1: MAIAK
|
||||
7F9B: BEDENNOVSK
|
||||
7F6A: PALAGIADA
|
||||
7F69: RYZDVIANAIA
|
||||
7F68: PEREDOVAIA
|
||||
7F65: GRIGOROPOLISSK.
|
||||
7F5C: NEVINNOMYSSK.
|
||||
7F2B: INOZEMTSEVO
|
||||
7F2A: MIN.VODY
|
||||
7F1B: ZHELEZNOVODSK
|
||||
7F17: BESHTAU
|
||||
7EFD: RASSHEVATKA
|
||||
7EBC: PIATIGORSK
|
||||
7EB2: KISLOVODSK
|
||||
7EA5: ZOLSKII
|
||||
7EA4: VINOGRADNAIA
|
||||
7EA2: KUMAGORSK
|
||||
7E9F: NAGUTSKAIA
|
||||
7E9D: KURSAVKA
|
||||
7E9B: KIAN
|
||||
7E9A: ZELENCHUK
|
||||
7E96: BOGLOVSKAIA
|
||||
7E95: OVECHKA
|
||||
8162: KURSHAVA
|
||||
7F97: CHUKOTSKII
|
||||
83AE: DEBRI
|
||||
8306: DEGTIAREVSKI
|
||||
8185: NIVA
|
||||
8160: OP 1814 KM
|
||||
815B: CHISTAIA
|
||||
7FA5: BERMEDSKII
|
||||
7F93: IZOBILNAIA
|
||||
7F64: KRASNOKUBANSK.
|
||||
7ED0: ESSENTUKI
|
||||
7EA6: APOLLONSKAIA
|
||||
@@ -0,0 +1,151 @@
|
||||
Filetype: Flipper NFC resources
|
||||
Version: 1
|
||||
# staID: Name
|
||||
9426: LADOZH.VOKZ.
|
||||
9421: MOSKOV.VOKZ.
|
||||
9423: VITEBS.VOKZ
|
||||
9424: FINLND.VOKZ
|
||||
9425: BALT.VOKZ
|
||||
948B: IM.MOROZOVA
|
||||
94B4: TOSNO
|
||||
94B5: SABLINO
|
||||
94BE: GORY
|
||||
94C0: IZHORY
|
||||
94C1: RYBATSKOE
|
||||
24C2: OBUKHOVO
|
||||
94C3: SLAVYANKA
|
||||
94C4: KOLPINO
|
||||
94C9: MSHINSKAYA
|
||||
94CC: STROGANOVO
|
||||
94CD: SUI'DA
|
||||
94CE: GATCHINA BLT.
|
||||
94D4: VYRITSA
|
||||
94D5: PAVLOVSK
|
||||
94D6: TSARSKOE SELO
|
||||
94E2: KALISHCHE
|
||||
94E3: LEBYAZHYE
|
||||
94E4: BOLSH.IZHORA
|
||||
9507: BELOOSTROV
|
||||
9508: ZELENOGORSK
|
||||
9511: RUCH'I
|
||||
9512: TOKSOVO
|
||||
9513: OSEL'KI
|
||||
9514: PERI
|
||||
9515: GRUZINO
|
||||
9516: VASKELOVO
|
||||
9517: OREKHOVO
|
||||
9518: PETYAJARVI
|
||||
9519: LOSEVO
|
||||
951A: GROMOVO
|
||||
951F: KUZNECHNOE
|
||||
95C3: KAPITOLOVO
|
||||
969A: MGA
|
||||
969C: LUGA
|
||||
969D: TOLMACHEVO
|
||||
969E: SIVERSKAYA
|
||||
969F: GATCHINA VRSH.
|
||||
96A1: OREDEZH
|
||||
96A3: SLANTSY
|
||||
96A5: ORANIENBAUM 1
|
||||
96A8: VOLOSOVO
|
||||
96BD: TIKHVIN
|
||||
96C0: VOLKHOVSTROY 1
|
||||
96C1: VOLKHOVSTROY 2
|
||||
96CA: VYBORG
|
||||
96CB: SOSNOVO
|
||||
96CC: PRIOZYORSK
|
||||
970A: BOLOTISTOE
|
||||
971D: STAR.DEREVNYA
|
||||
9721: 54 KM
|
||||
9723: KOLOSKOVO(79KM)
|
||||
972C: PARAVOZNY MUZEI
|
||||
9741: BOROVAYA
|
||||
97BB: DETSKOSELSKAYA
|
||||
9809: KUPCHINO
|
||||
9810: PETROKREPOST'
|
||||
9811: ALEKSANDROVSK.F
|
||||
9817: BERNGARDOVKA
|
||||
981C: PROSPEKT SLAVY
|
||||
9826: SOSNOVAYA POL.
|
||||
9829: PUDOST'
|
||||
982B: LENINSKI' PR.
|
||||
982D: PAVLOVO-NA-NEVE
|
||||
9835: LANSKAYA
|
||||
9838: KANNELYARVI
|
||||
983A: MELN.RUCHEI
|
||||
983F: PISKARYOVKA
|
||||
9849: UDEL'NAYA
|
||||
9852: BRONKA
|
||||
9853: VSEVOLOZHSK.
|
||||
985D: FARFOROVSKAYA
|
||||
9867: RZHEVKA
|
||||
9868: KOBRALOVO
|
||||
986B: AEROPORT
|
||||
9871: BRONEVAYA
|
||||
987B: SESTRORETSK
|
||||
9880: TATIANINO
|
||||
988A: KUSHELEVKA
|
||||
9890: RAKH'YA
|
||||
9894: UL'YANKA
|
||||
9899: PESOCHNAYA
|
||||
989A: NAVALOCHNAYA
|
||||
989E: STREL'NA
|
||||
98A3: ST.PETERGOF
|
||||
98A5: KOLTUSHI
|
||||
98A8: KUZ'MOLOVO
|
||||
98AD: KRASN. SELO
|
||||
98AE: SHUSHARY
|
||||
98BC: GORELOVO
|
||||
98C1: ROSHCHINO
|
||||
98CB: LISII' NOS
|
||||
98D5: DACHNOYE
|
||||
98D6: PREDPORTOVAYA
|
||||
98E4: NOVAYA OKHTA
|
||||
98E9: REPINO
|
||||
98F3: LEVASHOVO
|
||||
98FD: NOV.DEREVNYA
|
||||
98FE: IZHORSK.ZAVOD
|
||||
9908: VAGANOVO
|
||||
990C: SHUVALOVO
|
||||
9911: DUNAI'
|
||||
9916: IRINOVKA
|
||||
991C: TARKHOVKA
|
||||
9923: PUPYSHEVO
|
||||
9925: TAI'TSY
|
||||
9926: LAVRIKI
|
||||
992A: LADOZHSK.OZ.
|
||||
992D: ROMANOVKA
|
||||
9936: RAZLIV
|
||||
9938: KIRILLOVSKOE
|
||||
9939: OL'GINO
|
||||
993D: METALLOSTROY
|
||||
993E: KAVGOLOVO
|
||||
9943: VOZD.PARK
|
||||
9944: POST KOVALEVO
|
||||
9949: LAPPELOVO
|
||||
994D: KORNEVO
|
||||
9952: SOLNECHNOYE
|
||||
9956: MYAGLOVO
|
||||
995B: OZERKI
|
||||
995C: KOMAROVO
|
||||
9969: PROBA
|
||||
996A: KIRPICH.ZAVOD
|
||||
9970: KURORT
|
||||
9975: 67 KM
|
||||
9976: USHKOVO
|
||||
9981: DUDERGOF
|
||||
9989: UNIVERSITETSK.
|
||||
9992: YAKHTENNAYA
|
||||
9996: KOVALEVO
|
||||
9997: DIBUNY
|
||||
99AF: MANUSHKINO
|
||||
99CF: PARGOLOVO
|
||||
99D4: LIGOVO
|
||||
9C09: DEVYATKINO
|
||||
9C58: NEV.DUBROVKA
|
||||
9C5A: ALEKSANDROVSK.
|
||||
9C5B: GORSKAYA
|
||||
9D1C: NOV.PETERGOF
|
||||
9FDC: BORISOVA GRIVA
|
||||
98B7: SERGIEVO
|
||||
9957: LAKHTA
|
||||
@@ -77,6 +77,15 @@ void nfc_scene_mf_ultralight_c_dict_attack_prepare_view(NfcApp* instance) {
|
||||
// Set attack type to Ultralight C
|
||||
dict_attack_set_type(instance->dict_attack, DictAttackTypeMfUltralightC);
|
||||
|
||||
// Guard: if a previous write phase left a dict handle open, close it now.
|
||||
// Without this, navigating write->back->read->dict-attack would open the same
|
||||
// file twice, corrupting VFS state and causing a ViewPort lockup.
|
||||
if(instance->mf_ultralight_c_dict_context.dict) {
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
instance->mf_ultralight_c_write_context.dict_state = NfcMfUltralightCWriteDictIdle;
|
||||
}
|
||||
|
||||
if(state == DictAttackStateUserDictInProgress) {
|
||||
do {
|
||||
if(!keys_dict_check_presence(NFC_APP_MF_ULTRALIGHT_C_DICT_USER_PATH)) {
|
||||
@@ -167,6 +176,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE
|
||||
nfc_poller_stop(instance->poller);
|
||||
nfc_poller_free(instance->poller);
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfUltralightCDictAttack,
|
||||
@@ -199,6 +209,7 @@ bool nfc_scene_mf_ultralight_c_dict_attack_on_event(void* context, SceneManagerE
|
||||
nfc_poller_stop(instance->poller);
|
||||
nfc_poller_free(instance->poller);
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
scene_manager_set_scene_state(
|
||||
instance->scene_manager,
|
||||
NfcSceneMfUltralightCDictAttack,
|
||||
@@ -230,6 +241,7 @@ void nfc_scene_mf_ultralight_c_dict_attack_on_exit(void* context) {
|
||||
NfcSceneMfUltralightCDictAttack,
|
||||
DictAttackStateUserDictInProgress);
|
||||
keys_dict_free(instance->mf_ultralight_c_dict_context.dict);
|
||||
instance->mf_ultralight_c_dict_context.dict = NULL;
|
||||
instance->mf_ultralight_c_dict_context.dict_keys_total = 0;
|
||||
instance->mf_ultralight_c_dict_context.dict_keys_current = 0;
|
||||
instance->mf_ultralight_c_dict_context.auth_success = false;
|
||||
|
||||
@@ -1,38 +1,76 @@
|
||||
App(
|
||||
appid="subghz",
|
||||
name="Sub-GHz",
|
||||
apptype=FlipperAppType.APP,
|
||||
apptype=FlipperAppType.MENUEXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="subghz_app",
|
||||
icon="A_Sub1ghz_14",
|
||||
stack_size=3 * 1024,
|
||||
order=10,
|
||||
# Sources separation breaks linking with subghz on internal, commented for now
|
||||
# sources=[
|
||||
# "*.c",
|
||||
# "!helpers/subghz_gps.c",
|
||||
# "!helpers/minmea.c",
|
||||
# "!subghz_cli.c",
|
||||
# "!helpers/subghz_chat.c",
|
||||
# "!subghz_extended_freq.c",
|
||||
# ],
|
||||
# Sources separation breaks linking with subghz on internal
|
||||
sources=[
|
||||
"*.c",
|
||||
"!subghz_fap.c",
|
||||
"!subghz_gps.c",
|
||||
"!minmea.c",
|
||||
"!subghz_cli.c",
|
||||
"!subghz_chat.c",
|
||||
"!subghz_extended_freq.c",
|
||||
"!subghz_gen_info.c",
|
||||
"!subghz_txrx_create_protocol_key.c",
|
||||
"!subghz_scene_set_button.c",
|
||||
"!subghz_scene_set_counter.c",
|
||||
"!subghz_scene_set_key.c",
|
||||
"!subghz_scene_set_seed.c",
|
||||
"!subghz_scene_set_serial.c",
|
||||
"!subghz_scene_set_type.c",
|
||||
],
|
||||
requires=["region"],
|
||||
resources="resources",
|
||||
fap_libs=["assets", "hwdrivers"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Sub-GHz",
|
||||
sdk_headers=["subghz_fap.h"],
|
||||
# sdk_headers=["subghz_fap.h"],
|
||||
)
|
||||
|
||||
# App(
|
||||
# appid="subghz_fap",
|
||||
# name="Sub-GHz",
|
||||
# apptype=FlipperAppType.EXTERNAL,
|
||||
# entry_point="subghz_fap",
|
||||
# stack_size=3 * 1024,
|
||||
# sources=["subghz_fap.c"],
|
||||
# fap_icon="icon.png",
|
||||
# fap_category="Sub-GHz",
|
||||
# )
|
||||
|
||||
App(
|
||||
appid="subghz_fap",
|
||||
name="Sub-GHz",
|
||||
appid="subghz_add_manually",
|
||||
name="Sub-GHz Add Manually",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="subghz_fap",
|
||||
targets=["f7"],
|
||||
entry_point="subghz_add_manually",
|
||||
stack_size=3 * 1024,
|
||||
sources=["subghz_fap.c"],
|
||||
fap_icon="icon.png",
|
||||
fap_category="Sub-GHz",
|
||||
sources=[
|
||||
"subghz.c",
|
||||
"subghz_i.c",
|
||||
"helpers/subghz_gen_info.c",
|
||||
"helpers/subghz_txrx.c",
|
||||
"helpers/subghz_txrx_create_protocol_key.c",
|
||||
"scenes/subghz_scene.c",
|
||||
"scenes/subghz_scene_set_button.c",
|
||||
"scenes/subghz_scene_set_counter.c",
|
||||
"scenes/subghz_scene_set_key.c",
|
||||
"scenes/subghz_scene_set_seed.c",
|
||||
"scenes/subghz_scene_set_serial.c",
|
||||
"scenes/subghz_scene_set_type.c",
|
||||
"scenes/subghz_scene_save_name.c",
|
||||
"scenes/subghz_scene_save_success.c",
|
||||
"scenes/subghz_scene_show_error.c",
|
||||
"scenes/subghz_scene_show_error_sub.c",
|
||||
],
|
||||
cdefines=["SUBGHZ_ADD_MANUALLY"],
|
||||
fap_category="assets",
|
||||
)
|
||||
|
||||
App(
|
||||
@@ -58,6 +96,6 @@ App(
|
||||
targets=["f7"],
|
||||
apptype=FlipperAppType.STARTUP,
|
||||
entry_point="subghz_extended_freq",
|
||||
# sources=["subghz_extended_freq.c"],
|
||||
sources=["subghz_extended_freq.c"],
|
||||
order=650,
|
||||
)
|
||||
|
||||
@@ -77,9 +77,13 @@ typedef enum {
|
||||
SetTypeFaacSLH_868,
|
||||
SetTypeFaacSLH_433,
|
||||
SetTypeBFTMitto,
|
||||
SetTypeErreka433,
|
||||
SetTypeSomfyTelis,
|
||||
SetTypeSomfyKeytis,
|
||||
SetTypeKingGatesStylo4k,
|
||||
SetTypeBenincaARC,
|
||||
SetTypeJarolift,
|
||||
SetTypeDitecGOL4,
|
||||
SetTypeANMotorsAT4,
|
||||
SetTypeAlutechAT4N,
|
||||
SetTypePhoenix_V2_433,
|
||||
@@ -114,6 +118,19 @@ typedef enum {
|
||||
SetTypeNovoferm_433_92,
|
||||
SetTypeHormannEcoStar_433_92,
|
||||
SetTypeCardinS449_433FM,
|
||||
SetTypePujol433,
|
||||
SetTypePujol_Vario433,
|
||||
SetTypeET_Blue433,
|
||||
SetTypeET_Blue_Mix433,
|
||||
SetTypeATA_PTX4_433,
|
||||
SetTypeSeav433,
|
||||
SetTypeWisniowski433,
|
||||
SetTypeFadini433,
|
||||
SetTypeMc_Garcia433,
|
||||
SetTypeClemsa_Mutancode433,
|
||||
SetTypeDoormatic433,
|
||||
SetTypeElvox433,
|
||||
SetTypeVerex433,
|
||||
SetTypeFAACRCXT_433_92,
|
||||
SetTypeFAACRCXT_868,
|
||||
SetTypeGeniusBravo433,
|
||||
|
||||
@@ -496,14 +496,25 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
break;
|
||||
case SetTypeBFTMitto:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloqBFT,
|
||||
.type = GenKeeloqSeed,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq_bft.serial = key & 0x000FFFFF,
|
||||
.keeloq_bft.btn = 0x02,
|
||||
.keeloq_bft.cnt = 0x02,
|
||||
.keeloq_bft.seed = key & 0x000FFFFF,
|
||||
.keeloq_bft.manuf = "BFT"};
|
||||
.keeloq_seed.serial = key & 0x000FFFFF,
|
||||
.keeloq_seed.btn = 0x02,
|
||||
.keeloq_seed.cnt = 0x02,
|
||||
.keeloq_seed.seed = key & 0x000FFFFF,
|
||||
.keeloq_seed.manuf = "BFT"};
|
||||
break;
|
||||
case SetTypeErreka433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloqSeed,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq_seed.serial = key & 0x000FFFFF,
|
||||
.keeloq_seed.btn = 0x02,
|
||||
.keeloq_seed.cnt = 0x02,
|
||||
.keeloq_seed.seed = key & 0x000FFFFF,
|
||||
.keeloq_seed.manuf = "Erreka"};
|
||||
break;
|
||||
case SetTypeAlutechAT4N:
|
||||
gen_info = (GenInfo){
|
||||
@@ -523,6 +534,15 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
.somfy_telis.btn = 0x02,
|
||||
.somfy_telis.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeSomfyKeytis:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenSomfyKeytis,
|
||||
.mod = "AM650",
|
||||
.freq = 433420000,
|
||||
.somfy_keytis.serial = (key & 0x0000FFFF) | 0x00D50000,
|
||||
.somfy_keytis.btn = 0x04,
|
||||
.somfy_keytis.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeKingGatesStylo4k:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKingGatesStylo4k,
|
||||
@@ -541,6 +561,24 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
.beninca_arc.btn = 0x02,
|
||||
.beninca_arc.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeJarolift:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenJarolift,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.jarolift.serial = key & 0xFFFFF00,
|
||||
.jarolift.btn = 0x02,
|
||||
.jarolift.cnt = 0x03};
|
||||
break;
|
||||
case SetTypeDitecGOL4:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenDitecGOL4,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.ditec_gol4.serial = (key & 0x0000FFFF) | 0xCC090000,
|
||||
.ditec_gol4.btn = 0x01,
|
||||
.ditec_gol4.cnt = 0xC200};
|
||||
break;
|
||||
case SetTypeMotorline433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
@@ -627,7 +665,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x06,
|
||||
.keeloq.btn = 0x09,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Genius_Bravo"};
|
||||
break;
|
||||
@@ -671,6 +709,136 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Cardin_S449"};
|
||||
break;
|
||||
case SetTypePujol433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Pujol"};
|
||||
break;
|
||||
case SetTypeET_Blue433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x04,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "ET_Blue"};
|
||||
break;
|
||||
case SetTypeET_Blue_Mix433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x04,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "ET_Blue_Mix"};
|
||||
break;
|
||||
case SetTypeATA_PTX4_433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "ATA_PTX4"};
|
||||
break;
|
||||
case SetTypePujol_Vario433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Pujol_Vario"};
|
||||
break;
|
||||
case SetTypeSeav433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Seav"};
|
||||
break;
|
||||
case SetTypeWisniowski433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Wisniowski"};
|
||||
break;
|
||||
case SetTypeFadini433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Fadini"};
|
||||
break;
|
||||
case SetTypeMc_Garcia433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Mc_Garcia"};
|
||||
break;
|
||||
case SetTypeClemsa_Mutancode433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Clemsa_Mutancode"};
|
||||
break;
|
||||
case SetTypeDoormatic433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Doormatic"};
|
||||
break;
|
||||
case SetTypeElvox433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Elvox"};
|
||||
break;
|
||||
case SetTypeVerex433:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
.mod = "AM650",
|
||||
.freq = 433920000,
|
||||
.keeloq.serial = key & 0x00FFFFFF,
|
||||
.keeloq.btn = 0x02,
|
||||
.keeloq.cnt = 0x03,
|
||||
.keeloq.manuf = "Verex"};
|
||||
break;
|
||||
case SetTypeFAACRCXT_433_92:
|
||||
gen_info = (GenInfo){
|
||||
.type = GenKeeloq,
|
||||
|
||||
@@ -7,11 +7,14 @@ typedef enum {
|
||||
GenFaacSLH,
|
||||
GenKeeloq,
|
||||
GenCameAtomo,
|
||||
GenKeeloqBFT,
|
||||
GenKeeloqSeed,
|
||||
GenAlutechAt4n,
|
||||
GenSomfyTelis,
|
||||
GenSomfyKeytis,
|
||||
GenKingGatesStylo4k,
|
||||
GenBenincaARC,
|
||||
GenJarolift,
|
||||
GenDitecGOL4,
|
||||
GenNiceFlorS,
|
||||
GenSecPlus1,
|
||||
GenSecPlus2,
|
||||
@@ -52,7 +55,7 @@ typedef struct {
|
||||
uint16_t cnt;
|
||||
uint32_t seed;
|
||||
const char* manuf;
|
||||
} keeloq_bft;
|
||||
} keeloq_seed;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
@@ -63,6 +66,11 @@ typedef struct {
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
} somfy_telis;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
} somfy_keytis;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
@@ -73,6 +81,11 @@ typedef struct {
|
||||
uint8_t btn;
|
||||
uint32_t cnt;
|
||||
} beninca_arc;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
} jarolift;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
@@ -88,6 +101,11 @@ typedef struct {
|
||||
uint32_t serial;
|
||||
uint16_t cnt;
|
||||
} phoenix_v2;
|
||||
struct {
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
} ditec_gol4;
|
||||
};
|
||||
} GenInfo;
|
||||
|
||||
|
||||
@@ -59,11 +59,13 @@ SubGhzTxRx* subghz_txrx_alloc(void) {
|
||||
instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
|
||||
subghz_worker_set_context(instance->worker, instance->receiver);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
//set default device External
|
||||
subghz_devices_init();
|
||||
instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
|
||||
instance->radio_device_type =
|
||||
subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
|
||||
#endif
|
||||
|
||||
return instance;
|
||||
}
|
||||
@@ -71,12 +73,14 @@ SubGhzTxRx* subghz_txrx_alloc(void) {
|
||||
void subghz_txrx_free(SubGhzTxRx* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
|
||||
subghz_txrx_radio_device_power_off(instance);
|
||||
subghz_devices_end(instance->radio_device);
|
||||
}
|
||||
|
||||
subghz_devices_deinit();
|
||||
#endif
|
||||
|
||||
subghz_worker_free(instance->worker);
|
||||
subghz_receiver_free(instance->receiver);
|
||||
@@ -104,6 +108,7 @@ void subghz_txrx_set_preset(
|
||||
size_t preset_data_size) {
|
||||
furi_assert(instance);
|
||||
furi_string_set(instance->preset->name, preset_name);
|
||||
|
||||
SubGhzRadioPreset* preset = instance->preset;
|
||||
preset->frequency = frequency;
|
||||
preset->latitude = latitude;
|
||||
@@ -112,6 +117,65 @@ void subghz_txrx_set_preset(
|
||||
preset->data_size = preset_data_size;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint8_t tx_power) {
|
||||
#define PRESET_POWER_OFFSET_FM 8
|
||||
#define PRESET_POWER_OFFSET_AM 7
|
||||
#define TX_PATABLE_OFFSET_AM 8
|
||||
#define TX_PATABLE_COUNT 17
|
||||
|
||||
//I had to skip the +10dBM and -6dBm Values, use only ones AM/FM have in common.
|
||||
//Highest Value is 12dBm for AM, 10 for FM. So Menu needs to reflect that.
|
||||
static const uint8_t tx_pa_table[TX_PATABLE_COUNT] = {
|
||||
0,
|
||||
0xC0, //12dBm
|
||||
0xCD, //7dBm
|
||||
0x86, //5dBm
|
||||
0x50, //0dBm
|
||||
0x26, // -10dBm
|
||||
0x1D, // -15dBm
|
||||
0x17, //-20dBm
|
||||
0x03, //-30dBm
|
||||
0xC0, // 10dBm
|
||||
0xC8, //7dBm
|
||||
0x84, //5dBm
|
||||
0x60, //0dBm
|
||||
0x34, //-10dBm
|
||||
0x1D, //-15dBm
|
||||
0x0E, // -20dBm
|
||||
0x12, //-30dBm
|
||||
};
|
||||
|
||||
//Grab the AM and FM byte now, so we can do proper checks.
|
||||
uint8_t fm_byte = preset_data[preset_data_size - PRESET_POWER_OFFSET_FM];
|
||||
uint8_t am_byte = preset_data[preset_data_size - PRESET_POWER_OFFSET_AM];
|
||||
|
||||
//Set the TX Power Here in the CC1101 register...
|
||||
|
||||
//If we have both bytes 1st bytes set or none, this isnt a preset we can deal with here.
|
||||
if(fm_byte && !am_byte) {
|
||||
//Use FM Table
|
||||
if(tx_power) {
|
||||
preset_data[preset_data_size - PRESET_POWER_OFFSET_FM] =
|
||||
tx_pa_table[TX_PATABLE_OFFSET_AM + tx_power];
|
||||
} else {
|
||||
preset_data[preset_data_size - PRESET_POWER_OFFSET_FM] =
|
||||
tx_pa_table[1]; //Max Power 0xC0 10dBm
|
||||
}
|
||||
} else if(am_byte && !fm_byte) {
|
||||
//Use AM Table
|
||||
if(tx_power) {
|
||||
preset_data[preset_data_size - PRESET_POWER_OFFSET_AM] = tx_pa_table[tx_power];
|
||||
} else {
|
||||
preset_data[preset_data_size - PRESET_POWER_OFFSET_AM] =
|
||||
tx_pa_table[1]; //Max Power 0xC0 12dBm
|
||||
}
|
||||
}
|
||||
|
||||
//Pass back the preset_so we can call one liners.
|
||||
return preset_data;
|
||||
}
|
||||
|
||||
const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
|
||||
UNUSED(instance);
|
||||
const char* preset_name = "";
|
||||
@@ -249,7 +313,6 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
|
||||
|
||||
SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t repeat = 200;
|
||||
do {
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
@@ -259,10 +322,6 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
|
||||
FURI_LOG_E(TAG, "Missing Protocol");
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
FURI_LOG_E(TAG, "Unable Repeat");
|
||||
break;
|
||||
}
|
||||
ret = SubGhzTxRxStartTxStateOk;
|
||||
|
||||
SubGhzRadioPreset* preset = instance->preset;
|
||||
@@ -691,22 +750,28 @@ void subghz_txrx_set_default_preset(SubGhzTxRx* instance, uint32_t frequency) {
|
||||
subghz_txrx_set_preset(instance, default_modulation, frequency, NAN, NAN, NULL, 0);
|
||||
}
|
||||
|
||||
const char*
|
||||
subghz_txrx_set_preset_internal(SubGhzTxRx* instance, uint32_t frequency, uint8_t index) {
|
||||
const char* subghz_txrx_set_preset_internal(
|
||||
SubGhzTxRx* instance,
|
||||
uint32_t frequency,
|
||||
uint8_t index,
|
||||
uint8_t tx_power) {
|
||||
furi_assert(instance);
|
||||
|
||||
//Grab the prset name.
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(instance);
|
||||
const char* preset_name = subghz_setting_get_preset_name(setting, index);
|
||||
subghz_setting_set_default_frequency(setting, frequency);
|
||||
|
||||
//Get the preset data now so we can set TX power.
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, tx_power);
|
||||
|
||||
//Set the Updated Preset.
|
||||
subghz_txrx_set_preset(
|
||||
instance,
|
||||
preset_name,
|
||||
frequency,
|
||||
NAN,
|
||||
NAN,
|
||||
subghz_setting_get_preset_data(setting, index),
|
||||
subghz_setting_get_preset_data_size(setting, index));
|
||||
instance, preset_name, frequency, NAN, NAN, preset_data, preset_data_size);
|
||||
|
||||
return preset_name;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,15 @@ void subghz_txrx_set_preset(
|
||||
uint8_t* preset_data,
|
||||
size_t preset_data_size);
|
||||
|
||||
/**
|
||||
* Set TX Power
|
||||
*
|
||||
* @param preset_data Data of preset
|
||||
* @param preset_data_size Size of preset data
|
||||
* @param tx_power Menu Index of TX Power Setting. (Saves iterating in Config enter)
|
||||
*/
|
||||
uint8_t* subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint8_t tx_power);
|
||||
|
||||
/**
|
||||
* Get name of preset
|
||||
*
|
||||
@@ -390,7 +399,11 @@ void subghz_txrx_set_default_preset(SubGhzTxRx* instance, uint32_t frequency);
|
||||
* @param instance - instance Pointer to a SubGhzTxRx
|
||||
* @param frequency - frequency of new preset
|
||||
* @param index - index of preset taken from SubGhzSetting
|
||||
* @param tx_power - index of TX Power menu index option to use.
|
||||
* @return const char* - name of preset
|
||||
*/
|
||||
const char*
|
||||
subghz_txrx_set_preset_internal(SubGhzTxRx* instance, uint32_t frequency, uint8_t index);
|
||||
const char* subghz_txrx_set_preset_internal(
|
||||
SubGhzTxRx* instance,
|
||||
uint32_t frequency,
|
||||
uint8_t index,
|
||||
uint8_t tx_power);
|
||||
|
||||
@@ -125,7 +125,7 @@ bool subghz_txrx_gen_keeloq_protocol( //TODO lead to a general appearance
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_keeloq_bft_protocol(
|
||||
bool subghz_txrx_gen_keeloq_seed_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
@@ -142,7 +142,7 @@ bool subghz_txrx_gen_keeloq_bft_protocol(
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_KEELOQ_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NAN, NAN, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_keeloq_bft_create_data(
|
||||
if(txrx->transmitter && subghz_protocol_keeloq_seed_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
txrx->fff_data,
|
||||
serial,
|
||||
@@ -162,7 +162,7 @@ bool subghz_txrx_gen_keeloq_bft_protocol(
|
||||
|
||||
flipper_format_write_hex(txrx->fff_data, "Seed", seed_data, sizeof(uint32_t));
|
||||
|
||||
flipper_format_write_string_cstr(txrx->fff_data, "Manufacture", "BFT");
|
||||
flipper_format_write_string_cstr(txrx->fff_data, "Manufacture", manufacture_name);
|
||||
}
|
||||
|
||||
subghz_transmitter_free(txrx->transmitter);
|
||||
@@ -335,6 +335,36 @@ bool subghz_txrx_gen_somfy_telis_protocol(
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_somfy_keytis_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt) {
|
||||
SubGhzTxRx* txrx = context;
|
||||
|
||||
bool res = false;
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_SOMFY_KEYTIS_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NAN, NAN, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_somfy_keytis_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
txrx->fff_data,
|
||||
serial,
|
||||
btn,
|
||||
cnt,
|
||||
txrx->preset)) {
|
||||
res = true;
|
||||
}
|
||||
|
||||
subghz_transmitter_free(txrx->transmitter);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_kinggates_stylo_4k_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
@@ -395,6 +425,66 @@ bool subghz_txrx_gen_beninca_arc_protocol(
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_jarolift_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt) {
|
||||
SubGhzTxRx* txrx = context;
|
||||
|
||||
bool res = false;
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_JAROLIFT_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NAN, NAN, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_jarolift_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
txrx->fff_data,
|
||||
serial,
|
||||
btn,
|
||||
cnt,
|
||||
txrx->preset)) {
|
||||
res = true;
|
||||
}
|
||||
|
||||
subghz_transmitter_free(txrx->transmitter);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_ditec_gol4_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt) {
|
||||
SubGhzTxRx* txrx = context;
|
||||
|
||||
bool res = false;
|
||||
|
||||
txrx->transmitter =
|
||||
subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_DITEC_GOL4_NAME);
|
||||
subghz_txrx_set_preset(txrx, preset_name, frequency, NAN, NAN, NULL, 0);
|
||||
|
||||
if(txrx->transmitter && subghz_protocol_ditec_gol4_create_data(
|
||||
subghz_transmitter_get_protocol_instance(txrx->transmitter),
|
||||
txrx->fff_data,
|
||||
serial,
|
||||
btn,
|
||||
cnt,
|
||||
txrx->preset)) {
|
||||
res = true;
|
||||
}
|
||||
|
||||
subghz_transmitter_free(txrx->transmitter);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool subghz_txrx_gen_secplus_v2_protocol(
|
||||
SubGhzTxRx* instance,
|
||||
const char* name_preset,
|
||||
|
||||
@@ -63,7 +63,7 @@ bool subghz_txrx_gen_keeloq_protocol(
|
||||
uint16_t cnt,
|
||||
const char* manufacture_name);
|
||||
|
||||
bool subghz_txrx_gen_keeloq_bft_protocol(
|
||||
bool subghz_txrx_gen_keeloq_seed_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
@@ -108,6 +108,14 @@ bool subghz_txrx_gen_somfy_telis_protocol(
|
||||
uint8_t btn,
|
||||
uint16_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_somfy_keytis_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_kinggates_stylo_4k_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
@@ -124,6 +132,22 @@ bool subghz_txrx_gen_beninca_arc_protocol(
|
||||
uint8_t btn,
|
||||
uint32_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_jarolift_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_ditec_gol4_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt);
|
||||
|
||||
bool subghz_txrx_gen_came_atomo_protocol(
|
||||
void* context,
|
||||
const char* preset_name,
|
||||
|
||||
@@ -10,6 +10,7 @@ typedef enum {
|
||||
SubGhzNotificationStateTx,
|
||||
SubGhzNotificationStateRx,
|
||||
SubGhzNotificationStateRxDone,
|
||||
SubGhzNotificationStateTxWait,
|
||||
} SubGhzNotificationState;
|
||||
|
||||
/** SubGhzTxRx state */
|
||||
|
||||
@@ -1,71 +1,120 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 1
|
||||
IV: 4F 77 4F 20 66 75 72 72 79 20 49 56 20 55 77 55
|
||||
1D0560740B25F58EE0E85BF949139971E5AA08C5499CC74B11992D124C281012
|
||||
C05E2D2C715D8E24C518EF2841DA02173C05DD5BA5310EE85D09709500DB1726
|
||||
9EA5721836369FF918859077F50E33100F7AC53E8E8F31E25296579F875359DF
|
||||
D2A8AD1B65BC66B459525124CDC5011C79D98F542702FC69EABD64F908C0D80D
|
||||
2FA5F078BEB59851D42BC7E4E331AE3A8C384892DF003238CDA82450A6CD02AC
|
||||
E54ED5F49A093BC938521195C86FECF35FB6EC463C54C6E1609592DC5FA03CCB5E1EDA362FC9AB008C85E66B60147EBA
|
||||
048F4A28B18496487D65A924F4E37766C3563F41090D442DE61D7A5DD82F5FF1
|
||||
DA876A11401727E5102B578F87CCF9596AB9D9925FC90CDF99C9DEC7261B2C8C
|
||||
03D3842335D69A3AA42452274130B3FAAEE6087CB8D783B0E770062C034BB302
|
||||
7F50401E9FFAF10D1F61067C2E830EBBEC7C8B3B20F5C0AC2E10E68912BF2C82
|
||||
160BC0CA2FF01E076830F29846C1F6CCBAC7857F2043E8163449048BA8C99AA8
|
||||
09357F089CB148DDC578E0F11EC10659EF68A57440700F584922CB9842E2BF08AC977CCAA11355E89FA5C18113349F5E
|
||||
910D166F40F264225BD4C8EB16C5CC6374F8F8E1202D5BF28FA2E8BA38E420A36E67611E6D151051F7C74843E4A72BBC
|
||||
D9ACEBC528D7CA74B894A0378095E03C9BDBBE13DDA3FCD6D60CD3CAC49746B8
|
||||
90F94FD262E1859B7E5C08E7ED5B16CDB56D0E930034E315CD011DE3759150DA
|
||||
6620E176FC61DF250EF2BBC2AB27E0DA45A6E1E27F4E94405EE01C36E892EFBF
|
||||
7D6A062453236E4C353F19B875D079157D016B223B7D429C8F5BEA9D0F703EED
|
||||
20631589CB541B32C23CA7EA93B12E2016B5A90A2C9CAA6F5849C0B67158EA635858F26A86282C124AEA3FD31B7FD365
|
||||
E6CF167CEA0639D1504E0BFC8BAAA7FC7FA36A2286CBBE1312803422018A9F00
|
||||
0EB81E23FF37366BA4890CBD46BC8AF5A2CD56E9802B8DF5CDDD5114F677CDF6
|
||||
62195292F6F920DAF91F812FC3B94E8539C157D35D3BC94F2FB7A8481406C655AC0F112C4E10AC36892D43D95827BC1D
|
||||
65FB9BEBDB9BE975168D47B02CA8A2E0ADE2CD1949E90B06689B0475395663BD5AF3C8FEC42C4138CBBB8956AC55B475
|
||||
EF0A6961C754FCDBAE0222099DD8AA38DD6A92BCD53A576E105BC5BBD23400B7
|
||||
A608752C8602A5BD538441DE046DD7AE011FBA87210B57372BCBB471ECC2B720
|
||||
222C83DDC445F72152E563CD068851389364C1D83C9F7D6353325EFD553560B1
|
||||
96977544F2F821408C1A88FADB9B1E12D9CA97638A622190F83BC640508B6029
|
||||
B596A98BDDA5BD2FF1F812BE67FD00456D4D313E9497147E2439B51972B6B752
|
||||
FC276CAC90756F397C3BC616631CE8B9257E6C25D0DC15C5ED1CEB439ACF04D9
|
||||
383DD624B98E650E5B4BA28990B4D1912B785C689E6B6A05D77B47B6501CD98F
|
||||
410814DC8B38DD6EF781B55CA02730092F252082A77400AD90F22BF45A41C849
|
||||
8DA240E13E8B512B50FF4504A61037D0A3920B00523D51EEF9996CC3907C175E
|
||||
E655B7C31E346154C5EF7C59E3A710A2A2F145E7403E4ADB388B3A27D6FF59DE
|
||||
E2FB7F96EA16B20589995E95D0959B4ECC1EDDF86E347EB85FD29D0D5933A2A1
|
||||
00D910F2B050900735A8446220FF7321813252862A69C05A7A534118E50E61DB
|
||||
BA1B9E7E26E04542183F085060421553CDD3FD9034AAAFCB7EE980A68B98087E
|
||||
216EBA33FCE4B834BF64621E557923D8AE41F5895266B7BBDFCA6EAF985F036E
|
||||
2E9075A45ED6D86C172C9ADAAF5E991DA8DA9CBF2F24D746D22A331E236FBA4A
|
||||
04E4B185C150AF45A67E15D68282C7558B13BFFB05BFCB71BCFD2B92DE5D9701
|
||||
1FDC4D759EA89681F76A8F7D336118FE6801EFD10D73925C2749775D9DFED282
|
||||
FFB32167FBF860418AAABF29B0D4FEF57BB07454ECF4BD2CF175D44E84C04CED
|
||||
7C6E419658872D298F2E7B02568B9ED870FDFFE5082ED0BBE689FD04EFFFE7E7
|
||||
ADAB0F3108398C75ECC6D2E572960B5685C336DDF3D6F5B9C12D069F27BA15A8
|
||||
DAC772C1A81181738CDB8C0E89C2E5B7A57E2DA65CA15232DE96A3C4A599A0E9
|
||||
7F6204274D90E88B3F5D5AC86EEEE76C27C0D083E79ADA7BCF7D060FE6F05A3E
|
||||
5BAB4CCB593418CD8965C09C0925EDB78BB4C8A10892F264DF12F50E532F006E
|
||||
7C67525C921ABAEC4BFAA376162A7B2B2827AE4C9840DC37F067FE3B72BE7304B6EEB5FB1AD17CFF5F079EFECC04AD56
|
||||
94FF6DD4CE63381778E86E61423EDDDD9CC71C3462D66F1AD9A0AD5D378AECE2
|
||||
E9CA5BA5C6D6101EA3A51F28E48D49789D60273A9F70D56020D003265517AFA3
|
||||
EBF55649D226E9FDAAB57C2E1D75E3FD3A8D216488E97D00E932B3542E731D20
|
||||
2B576C3616DDAFEE23A8ECE24BBD89590C8C2D551F14D8EFA4DBB30216F89C31
|
||||
EE8D3CF3C503DE4FF368F23585F1A7017FF66A910696A6760AFD2B9822911960
|
||||
A32910F791C5EE9998DCE5371B3252427315C9D11AD506CE65760611D873C81D
|
||||
740646D11A32C65A8549B3AEA8A499866C35D926B2BA21ED73934AA37ADCC1E6
|
||||
0E66EF4CA934A5D1ACED28CBEEAA3AF7941E10918DA79379090B6339F11E267E
|
||||
D79D8666FD947B0D0D504FF10B048B147CB000AD8CDC1F0DEB395FB72B789963
|
||||
2F7BA07F18F4A91AEDC08867E9CC4B8689B0831A7CE0E0AEF3D92C0CA9BBC698
|
||||
1AB4351CFF02CC600C972CE87F69B23F8ECCC32A90BD5F429F8017A80306F23D
|
||||
ED2AD447E7DF7A34D78A313395FA1C3AF63CE02B77A5B08CE19493CFA1173232
|
||||
C8C8DEAF10AA3994EE7D6DC8E1EB403042627E0F3524409F40C03A7C0C106A80
|
||||
5778B4A3E7BE82C07BA6A311A87649F3C7AE5107A89571E14AEF05B9E285C87A
|
||||
30080347DB3B580B18E8EAC66E1B7227F791773F0342EE0DF8267EC993EC3F24
|
||||
2DB3B2A17C165B5C6A1D4944A5B595016588F028DD4F763C4ED6B7FE7849E918
|
||||
C1F0CF343B77F31D9A2E810821393EE9D1E0D4B54A87B2DD8CCFBB16FBD77A75B50A0E78D1E8A86310572443731B9DB5
|
||||
88EF373C37AADAE1155E7DBDBB7E0B048E3BFEFB412DC49EA8A48E1544B6DC87
|
||||
98694781F3EE698ABA8D2CDFFB1CA0425AA17BFE904FC7812E65A78DF1CA06C2
|
||||
6BBBCA6672311E1A3BF7001B3222890C8A68A8B7D87DE91624BB9D1FEC0E2728
|
||||
4550A44B654085C3A3620B5D4D2C6A7F962275BC5926B9B7E3A706F128BF6D6C967E2BF2ACD4DD000BDC8BDE69684F6F
|
||||
IV: 2E 2E 2E 4D 65 6F 77 20 4D 65 6F 77 20 4F 77 4F
|
||||
411F604B4F4487BEFB5285C7023822BE66D08DD0AAB0B659D58A35E68850BD85
|
||||
12BB80D35A9750D218BDE6D4BE4E5FD3B5CE20EDA464E1FA80A000D49FF567B4
|
||||
81B613E6CFD99287BCBF0957733704E61B4FFE9CBEED2DC721FBABFEB1A249CE91EEEAE285FC10E0A83A6E1BBEA7EB56
|
||||
421C18DF4D8280560EFADDBF0BB02F90942CC5B5B2986327D96F5446CC531BD0
|
||||
8BB08D9758943404ACE935B58640E78478C91140E5871D7D070B104DE470C3F2
|
||||
1E45CCF03C56DDA4403214A430330647D58D443F665794F5F04D9701C6F32AA2
|
||||
4429DDE801D7EB69ADD042A70F83AD6160E8504D6C28A9A4A3D7531140549343
|
||||
64BC31C7E98E5223E7D74B844CF4BA26594119D3E468188123D6056C52AF11E7
|
||||
F7133D37B1A05BCFB3267D7659B85598E3B2B23502C090C042F0AAEB84E4326D
|
||||
97FA5E05D70495342BACADA35918C339518DC3EC4AC94F94BC73A08BC539A80F
|
||||
45F22E4ED7559C875725F2DFCF2DFC0631DF1565A12F455F13C4A101A54E607B
|
||||
95C78F6417110955813DA8E4AFDCF118A1E19006B4D627FCD1B1688F16CE1933
|
||||
7B19D2DA8DEA82083E9F52CED668A0CD6B997AED26F40F6BED30EE7D0E3BD117
|
||||
A9B4AECEB0C40BFE38F06302769702C89CF530C16308D0DB7A74AF9B70D3034B
|
||||
D97B2D17062FD5264C88A2369ACA8BBA064B9B99D064C8831B12B1A23E51BEC1
|
||||
A26401DADE20E5D99F53703D17B6E247D169FE0073512A23F6EDB8B3FD9AA73E
|
||||
FFA8F754EC999FA789C333D7C2EEE62CE7ABEFEF9EDA191DDF9C0BEE59E30927
|
||||
82FB6C3FB0AD818834B4335855599741728466E3418EBB0C03382C01F2CFE517
|
||||
2D284BC94C8C3EAE99FB8060524540C1FA07866A648CD49B82B874DF2073691E
|
||||
5EFE887394C60DADCDBE4E25E323CAFDC8E0E5A435FE4941CE29274A319E2F4B
|
||||
8B3CD3C3310933B59C328892998E434CF34F5230B219B9F00EE888154F71D234
|
||||
FF176D5DD6D6C296498E40E30FFF2F8498760ED5609B2E3080661A29B1F3C7E7
|
||||
CFFD9A9B1210EE4B49849E00B8513400B5399BEDCC98D8C1CEFB5F062BA0F2D7
|
||||
99DACFB462669C3F2C87C09F2B3FB0AC8F9F26DEDF6D04CF0C306F7F91615FCF
|
||||
47A99EB39C8655BD8CB93D349478514E0ECA46C6463011B6B1F0A9B807DEF300
|
||||
CC2EDDB4A96311B0A3D4A55F485292CF7585E1CAE9D3D46ADBD40A3391396DFF
|
||||
82DE652C1008367C54C8C431DADC5026CCF1B300BB112D2F4B3366B319F61284
|
||||
71BBA17FDC6261801D17434538524D3B6EE42355B8ABF99C07DBD69F3C1031CE
|
||||
974DC30EDA9E1CA01C0532E82593D7D84E7FE282659EDAA8FC3474DA422D57B2
|
||||
6975A600597A196A21D8432881D95D977C168027D9FFAB273DE00A5AEE3BC5E1
|
||||
0B7EC247D17B8C80E151E9DD60B94D2D6CD4E7A03E6404FA044D6BC2A15CF214
|
||||
73D12A173C2B97A77231901E94B1C81EA1AF79BD1EA01F8F8E2A3B38177A84EC
|
||||
9D309CE4B225FC251019B1354E8AEB47EE1D6FA792FF0E14FAFB0C0944D79350
|
||||
816B8EB8CB5ADEAC80C9359F9BF538A6641A20AB8BE422AD65E50802DFF38CED
|
||||
1F41BBFFA9338DB4143D4D3CBA84637FB397EEA36D5D89D4A5D08B3FB8181540
|
||||
295359A1E73F5F94186DBFC13175A6DA08771483043408C145B0FB4A10716C86
|
||||
990E0B2ABE53757378385879D4D5BBCEF45A69F659963DB6813931973B69ABD6
|
||||
6509E69551E5D3E38349DC16CBABBD059DB80852ECA56F7902DEE11F280B14B6
|
||||
C8D22F8E7592087432BC4FF32D1810697406948F2C69C54BC5CA8F59BE65278F
|
||||
E53840FE4593AB7708C14E3E1A33D689160C018CF2CEC3A6FB309929BDA2A66F
|
||||
3CE77B617C5E133046704DAE55B5328C861CE02F223CE75E6CDFF296927D0012
|
||||
0CD25E6B29860621BED286C97675EC6515D9A93B7BCC5E8571460DF90A73F748
|
||||
C387DAC55E4B8F15BCB228CF58F857419C91BAF619D38D2700E7149BE56C9D37
|
||||
E1AEB1E36C5C0D30B858101BE459E29CE2A49470022495A8D4869D44826F7928
|
||||
173D2BB833DCB4291D25E43C9E85AA7900198BF92D6E97EE861BBF6CBE1D2147ADABA5DAEA976F6D2A2716B7E3E75DEC
|
||||
A388246FAB037912C9B1D2D25C144795B16E917BD2653693346E1A076718FC03
|
||||
4EA223311825F64F88AB509D69B708A2143A76A180ED1AB46C8F1AA1C4A9E5AF
|
||||
0CBBC26D7C458E415DCEECC9F73008B556BED1B77A3AA6BE8C557B46058180D9
|
||||
9DE0FFD924900E5B3FD4CCE2B69D7B20FF52EE1E747FFACC6A631056D7D2B4CE
|
||||
2C05D81B20DA46ED217BE759EFD95919D434FAC98B08405FB9604B61982E4141
|
||||
4EEBB881DBE7A97C9C043221B2038F8B919C144D33ACDCE0F9AB5A2D63363E07
|
||||
F19CDBD22DCC70D2A11AD7D6210FEA7601BA2093F1E9766C4F8452F1AD14046B
|
||||
AA165E848FB25FAF3AC74D1AD809BA2A45FE2E79090256BF9E77BDDCD7A4A4E2
|
||||
C63B40C559AE339DB491E1E5B39C0EB321EC3A3BD2441763A5B2DF7C2DF534F4
|
||||
DA1CEA4B84C64D744AC3BE1D29B1E752CCFB3CFAA56AEB85C1BED37293BEAF93
|
||||
C67E0C619482F59D63C6B097347A4C777EBC60D800DD586C10A0BCEC67628618
|
||||
0EDE32F4FB6ED09526381B638A7CA5869C7B20FE6E003311065398E3ED3E3474
|
||||
43714DC1C28BC0FD3DDD348E93169523D6BC4C8AC1F62545C44D405ED1CA4927
|
||||
B49A42DCCC1A76C1643AD96CAE981BE430BEC9CA372A115C02E87BECE4FFFAA6
|
||||
E66E1F05704B9BE33AC1A52D33B3CE1D373106B61C10252E56618F64FB2E665B
|
||||
A78DD00AF303F280CACF7DC5C91778096D77989AAE29EDDC3D772EA32A6A5CB1038196BB1C2394C3E09A706EB421447F
|
||||
7EC357770322CFB212E77C7A1557C46AF1048A87CB14AE0339FF18E031927DD9
|
||||
698F99BEC7BBDA2F06BA8F393235783C05F1E324CB89A2F9239A93C63A96D657
|
||||
5955B7E969ECD7E5DDAB6AC8C103CD1C03504A8DA71346452302887FD72ED9D4
|
||||
A6856ADA5AD21EAC6255C1A3166CBAFC8763DD6BB4F2AB42BCCB1B71D02B722C
|
||||
DB86290A20BD5A1CAFED7D06511096B32BAA7CAEDC49A53488542F3944B72531
|
||||
83B8E69B9A50EB7D3F51D4221C4002110AB129A5E32A5CF07763BF5A198EBC68978CCF097D4396AFD920DD5A23C4DE14
|
||||
95C4CA690A9214E66520F67150A42E5ADE143E9C8D0C5B9CFABDFC93D193FC3D
|
||||
A3B3E5E7B57F758FDB2062613DC7CF3990DAEF6F2F1EABCD169EFA85C1237F8D
|
||||
2FD52B5A55EA063680E6CD164F6CD50148E6CBF0DAD1250494CF0E536D39687D46876E1E2C0A9902F785C60273BB5026
|
||||
38F372E8F07F16FB50942FFAC6FC4D49601CF09A65C714AC0998EB1FE0DD8F5A
|
||||
94B863DA82DD1BD27D083690AB4B59D9B6236FCB1172B9480F2E11BC420FC46C3D554B78B2476C964379E028DC89BA29
|
||||
4D52AAF64319E435FDD046276964E7A819A3CCF9EEC576A0993FE6F8E29095BB
|
||||
F44CE71DE07CFAD16BEE0041832A532EA266557C640B4FAE6245252B47C6B8C05AFD9C6F305596ECF68BFE8829B9032D
|
||||
8520769CE953F5F02C132DE95141E8F78FF2C846B9F672970771BBFCB51386BF
|
||||
FAA8C71F036FD5A1BDCFCEC05E890BCCA8EB92E7AABF4C7D64D1EA3D25730DD0
|
||||
CDB89D5B8B73DB2013EDBB34D8444B37EBAA308345A9626E153FF0A9574FC4D6A2B9588BC20EC0D9B1498228F116D3F6
|
||||
13351D444888E92AD03EFFE3466EFD5719DA41FB5BA0805C09CF3E79B188223B
|
||||
B36B0913A714AB0DD7C0005426127B3A2C02DD3112E745064D7A5899DE26A128
|
||||
C3AB7BBA577D74FD581FDA6BA17C1D2E1EBD4A4AA5CB42852E59CEE91D81B3E263549755171B22A5FBF4A1156C8C0A7B
|
||||
18F579227253BAB1E3CA2E71331C6090286480D19BB1FEF242793D27BCA157D3
|
||||
BF613CE44B9BDA278D884D2F756FBE164A9736D28A52B0101611D61B643EB978
|
||||
9DE9087BD023D6F6ABDF4A345E85A1110D6B205CCC31DC7372126CCC5EC618EF
|
||||
764602226E608E5371E693185D57B7C4D6FE2CBDA379D986A251CC7286B283FEE35E07E5B32B93649D52D9CE4FFC90A7
|
||||
3AA3162A345891481F09C9231238AD644B6804643C5EED165B3B3F078E0FC216435AE2385B3AF4EB44C029D9A60FC5D9
|
||||
867DD29F637DDFBE24CA6AFDA94366D865D701CBCAA9E7074B6FB29592AE31933F230C6471C5A513D19560F5E0820BCB
|
||||
EDB4BC2F025C11311527B227E42726C3BB4A6CFF49898E2FFEB2097F0924E8EE
|
||||
D54BFE2E7C21D84AD967433C7329BBBD257A026BFC7866071C80396B45E764D6
|
||||
ED13A30EC29B22AB34D5EFE27C74C112ACD42CFFFD0695CE743FE2D0ECCCB35EF58222519E54289E7AD0624A11698986
|
||||
84AF1F78D666058274DE1896D706E6196B55B782C395216915C1023E1E44F4A2
|
||||
648F762C38B32B930029E5730609A46CF0BCB4117BEF41D36ED47BE8EB8DB2AC
|
||||
745FAFAFC79AD2DFBE3B03387EE5C3FC05EBF1A61A371E439B7FA977A1ACA3B0
|
||||
8C4FE20E0F71A9822FD32A866571D9ED6F8A1F393430AF5DB5FDBF4F7030165A
|
||||
CC54F712C0C63A6B207C9ACAD7943BDCCC0F8126449E82CA5681F88A0491625B
|
||||
9F1654268308E44DD6A9128AB1BF9B01D521D5BA9A98E5A77D35564C3678D9EAF2C8A2EC09FC3E5CD1A831FDB95486D0
|
||||
58E094157F881A156E0D4EBB79ABFED8D855A9BE1589471A8BDA6AA9F9C58F4B8BF6B326653B6CE28C45DDC3DE4CC4BE
|
||||
B3BE094F6E487FD211792E424BB5EB95FC11AC78832FE9EAFDB076867A9B4AA2
|
||||
9FC6BE151041C150003E5BA940ADABDD02A080A9F4EE61215A6162BB1C13B7D9
|
||||
7CFE8E8FE7E2218425F581F70AC6E043E9253774A80450113C836EBAF4EAF4A0
|
||||
8610A9694E586306A4078E5DD3C757870C21130D6F67593994568FA1E7B7F4CA
|
||||
774DDBA8E8AA78BFF82B1E9C8CC58873C73AD0B977ED4EDD465513F9D238AA7DB2BE64536F4950BDAA00F971551F0922
|
||||
DB103B1AB06E9115193F5DC6F0C2D806AE9518C91D7D72945B27BE86B2D16351
|
||||
86C4A3F2ACBFA942CF4FF3DC062A4786D9D005C05671E232D93DEBCF5C1D7B96
|
||||
909EB49451C1D7A1B6E01FFC55EED8DA3C5DB4AFEA5C347DD4E79642746C963E64113CEDECF951947C095B12390BA10F
|
||||
AD7CFDF6D5C93806F8E5321EC2B4E2C6C7B750874E046154EC76EC2678B6F0C938D2D99A6646663ABC730190C7108C1F
|
||||
F3DC34F33197A7D2DCBA4D25ACC6EAA0612723C92BFF226E7B3A73BD07B2EBEA
|
||||
B7074A0B79FE44FA94C3EC55C2C694ADD593C021DED43085CB5D69DBA7F6F6F1
|
||||
9C7B26FC47E16E41E8F1F560EBEE3B580E5F44C1A2DA0BA415C279530416F231
|
||||
EBED9733677546781E77DC9E6668AE3CA6D1B6C0E1446EE10EBEC8F8ABF5228C
|
||||
D21EB73FE8C693DD4C580A51DB46D5DF26D798A8A67095FF087B8F14907993C2
|
||||
CB9D919298CB3EF9458A962BDD79736F6FC00E209B389C0543D06687AB16ADE0
|
||||
EED58F3D2003F1F288718544B95F8F7728AB6D0A7D10E3BEFED3C7F4A55A6B7CCBF8EACE17F0FADCDA6B19DBA74A2324
|
||||
D4E83675B9B5BB81D1E5C17CB88225C514C6DD2852D22A8C4681F0683248B21E
|
||||
0949495177B2C21F42679921EA4C83CD7A9700FB34B13DDF3E61CE26162DE0AF
|
||||
2C38210508654DE730157BA138118793DDA2BCD7958EDD21B02796B689D0990C
|
||||
47C78252386879DB6F9F3516FBCA29BAD3EB5F837B881422D7D421A41CCB851EFEA209D371E6E81EF957441D31D71842
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
ADD_SCENE(subghz, start, Start)
|
||||
ADD_SCENE(subghz, receiver, Receiver)
|
||||
ADD_SCENE(subghz, receiver_config, ReceiverConfig)
|
||||
ADD_SCENE(subghz, receiver_info, ReceiverInfo)
|
||||
ADD_SCENE(subghz, save_name, SaveName)
|
||||
ADD_SCENE(subghz, save_success, SaveSuccess)
|
||||
ADD_SCENE(subghz, saved, Saved)
|
||||
ADD_SCENE(subghz, transmitter, Transmitter)
|
||||
ADD_SCENE(subghz, show_error, ShowError)
|
||||
ADD_SCENE(subghz, show_error_sub, ShowErrorSub)
|
||||
ADD_SCENE(subghz, saved_menu, SavedMenu)
|
||||
ADD_SCENE(subghz, delete, Delete)
|
||||
ADD_SCENE(subghz, delete_success, DeleteSuccess)
|
||||
ADD_SCENE(subghz, set_type, SetType)
|
||||
ADD_SCENE(subghz, set_key, SetKey)
|
||||
ADD_SCENE(subghz, set_serial, SetSerial)
|
||||
ADD_SCENE(subghz, set_button, SetButton)
|
||||
ADD_SCENE(subghz, set_counter, SetCounter)
|
||||
ADD_SCENE(subghz, set_seed, SetSeed)
|
||||
ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer)
|
||||
ADD_SCENE(subghz, radio_settings, ExtModuleSettings)
|
||||
ADD_SCENE(subghz, read_raw, ReadRAW)
|
||||
@@ -27,3 +18,16 @@ ADD_SCENE(subghz, need_saving, NeedSaving)
|
||||
ADD_SCENE(subghz, rpc, Rpc)
|
||||
ADD_SCENE(subghz, show_gps, ShowGps)
|
||||
ADD_SCENE(subghz, signal_settings, SignalSettings)
|
||||
#else
|
||||
ADD_SCENE(subghz, set_type, SetType)
|
||||
ADD_SCENE(subghz, set_key, SetKey)
|
||||
ADD_SCENE(subghz, set_serial, SetSerial)
|
||||
ADD_SCENE(subghz, set_button, SetButton)
|
||||
ADD_SCENE(subghz, set_counter, SetCounter)
|
||||
ADD_SCENE(subghz, set_seed, SetSeed)
|
||||
#define SubGhzSceneStart SubGhzSceneSetType
|
||||
#endif
|
||||
ADD_SCENE(subghz, save_name, SaveName)
|
||||
ADD_SCENE(subghz, save_success, SaveSuccess)
|
||||
ADD_SCENE(subghz, show_error, ShowError)
|
||||
ADD_SCENE(subghz, show_error_sub, ShowErrorSub)
|
||||
|
||||
@@ -45,48 +45,27 @@ bool subghz_scene_more_raw_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(!subghz_file_available(subghz)) {
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneMoreRAW, event.event);
|
||||
if(event.event == SubmenuIndexDelete) {
|
||||
if(subghz_file_available(subghz)) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDelete);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW);
|
||||
return true;
|
||||
} else {
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneReadRAW, SubGhzCustomEventManagerNoSet);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDeleteRAW);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexEdit) {
|
||||
if(subghz_file_available(subghz)) {
|
||||
furi_string_reset(subghz->file_path_tmp);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexEdit);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
return true;
|
||||
} else {
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
}
|
||||
furi_string_reset(subghz->file_path_tmp);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexDecode) {
|
||||
if(subghz_file_available(subghz)) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneMoreRAW, SubmenuIndexDecode);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDecodeRAW);
|
||||
return true;
|
||||
} else {
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart)) {
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
}
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDecodeRAW);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -76,6 +76,20 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = {
|
||||
-50,
|
||||
};
|
||||
|
||||
//TX Power
|
||||
#define TX_POWER_COUNT 9
|
||||
const char* const tx_power_text[TX_POWER_COUNT] = {
|
||||
"Preset",
|
||||
"10dBm +",
|
||||
"7dBm",
|
||||
"5dBm",
|
||||
"0dBm",
|
||||
"-10dBm",
|
||||
"-15dBm",
|
||||
"-20dBm",
|
||||
"-30dBm",
|
||||
};
|
||||
|
||||
static void subghz_scene_radio_settings_set_device(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
@@ -90,6 +104,20 @@ static void subghz_scene_radio_settings_set_device(VariableItem* item) {
|
||||
subghz_txrx_radio_device_set(subghz->txrx, radio_device_value[index]);
|
||||
}
|
||||
|
||||
static void subghz_scene_radio_settings_set_tx_power(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
|
||||
//Update the Menu Item on screen
|
||||
variable_item_set_current_value_text(item, tx_power_text[index]);
|
||||
|
||||
//Set TX power and remember setting
|
||||
subghz->last_settings->tx_power = subghz->tx_power = index;
|
||||
|
||||
//Save the settings now, this is the convention here!
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
static void subghz_scene_receiver_config_set_debug_pin(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
@@ -175,6 +203,18 @@ void subghz_scene_radio_settings_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, radio_device_text[value_index]);
|
||||
|
||||
//Add TX Power
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"TX Power",
|
||||
TX_POWER_COUNT,
|
||||
subghz_scene_radio_settings_set_tx_power,
|
||||
subghz);
|
||||
|
||||
value_index = subghz->tx_power;
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, tx_power_text[value_index]);
|
||||
|
||||
item = variable_item_list_add(
|
||||
variable_item_list,
|
||||
"GPS Baudrate",
|
||||
|
||||
@@ -111,7 +111,8 @@ void subghz_scene_read_raw_on_enter(void* context) {
|
||||
subghz_txrx_set_preset_internal(
|
||||
subghz->txrx,
|
||||
subghz->last_settings->frequency,
|
||||
subghz->last_settings->preset_index);
|
||||
subghz->last_settings->preset_index,
|
||||
subghz->last_settings->tx_power);
|
||||
}
|
||||
}
|
||||
subghz_scene_read_raw_update_statusbar(subghz);
|
||||
|
||||
@@ -97,9 +97,9 @@ static void subghz_scene_receiver_update_statusbar(void* context) {
|
||||
} else {
|
||||
subghz_view_receiver_add_data_statusbar(
|
||||
subghz->subghz_receiver,
|
||||
"",
|
||||
"",
|
||||
furi_string_get_cstr(history_stat_str),
|
||||
"",
|
||||
"",
|
||||
subghz_txrx_hopper_get_state(subghz->txrx) != SubGhzHopperStateOFF,
|
||||
READ_BIT(subghz->filter, SubGhzProtocolFlag_BinRAW) > 0,
|
||||
show_sats,
|
||||
@@ -255,12 +255,16 @@ void subghz_scene_receiver_on_enter(void* context) {
|
||||
|
||||
if(subghz_rx_key_state_get(subghz) == SubGhzRxKeyStateIDLE) {
|
||||
subghz_txrx_set_preset_internal(
|
||||
subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index);
|
||||
subghz->txrx,
|
||||
subghz->last_settings->frequency,
|
||||
subghz->last_settings->preset_index,
|
||||
subghz->last_settings->tx_power);
|
||||
|
||||
subghz->filter = subghz->last_settings->filter;
|
||||
subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter);
|
||||
subghz->ignore_filter = subghz->last_settings->ignore_filter;
|
||||
subghz_txrx_receiver_set_ignore_filter(subghz->txrx, subghz->ignore_filter);
|
||||
subghz->tx_power = subghz->last_settings->tx_power;
|
||||
|
||||
subghz_history_reset(history);
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateStart);
|
||||
|
||||
@@ -207,6 +207,11 @@ static void subghz_scene_receiver_config_set_frequency(VariableItem* item) {
|
||||
frequency / 1000000,
|
||||
(frequency % 1000000) / 10000);
|
||||
variable_item_set_current_value_text(item, text_buf);
|
||||
|
||||
//Set TX Power
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
|
||||
|
||||
//Set the preset now.
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(preset.name),
|
||||
@@ -235,14 +240,14 @@ static void subghz_scene_receiver_config_set_preset(VariableItem* item) {
|
||||
variable_item_set_current_value_text(item, preset_name);
|
||||
//subghz->last_settings->preset = index;
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, index);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
preset_name,
|
||||
preset.frequency,
|
||||
NAN,
|
||||
NAN,
|
||||
subghz_setting_get_preset_data(setting, index),
|
||||
subghz_setting_get_preset_data_size(setting, index));
|
||||
subghz->txrx, preset_name, preset.frequency, NAN, NAN, preset_data, preset_data_size);
|
||||
subghz->last_settings->preset_index = index;
|
||||
}
|
||||
|
||||
@@ -268,6 +273,9 @@ static void subghz_scene_receiver_config_set_hopping(VariableItem* item) {
|
||||
(frequency % 1000000) / 10000);
|
||||
variable_item_set_current_value_text(frequency_item, text_buf);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
|
||||
|
||||
// Maybe better add one more function with only with the frequency argument?
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
@@ -453,7 +461,8 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
subghz_txrx_set_preset_internal(
|
||||
subghz->txrx,
|
||||
SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY,
|
||||
SUBGHZ_LAST_SETTING_DEFAULT_PRESET);
|
||||
SUBGHZ_LAST_SETTING_DEFAULT_PRESET,
|
||||
subghz->tx_power);
|
||||
|
||||
SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
@@ -477,6 +486,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
subghz->repeater = SubGhzRepeaterStateOff;
|
||||
subghz->last_settings->delete_old_signals = false;
|
||||
subghz->last_settings->autosave = false;
|
||||
subghz->last_settings->tx_power = subghz->tx_power = 0;
|
||||
|
||||
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
|
||||
subghz->last_settings->enable_sound = false;
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include <lib/subghz/blocks/custom_btn.h>
|
||||
|
||||
#include "applications/main/subghz/helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
|
||||
#define TAG "SubGhzSceneReceiverInfo"
|
||||
|
||||
void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) {
|
||||
@@ -36,6 +39,10 @@ static bool subghz_scene_receiver_info_update_parser(void* context) {
|
||||
|
||||
SubGhzRadioPreset* preset =
|
||||
subghz_history_get_radio_preset(subghz->history, subghz->idx_menu_chosen);
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
subghz_txrx_set_tx_power(preset->data, preset->data_size, subghz->tx_power);
|
||||
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(preset->name),
|
||||
@@ -140,32 +147,29 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
}
|
||||
//CC1101 Stop RX -> Start TX
|
||||
subghz_txrx_hopper_pause(subghz->txrx);
|
||||
// key concept: we start endless TX until user release OK button, and after this we send last
|
||||
// protocols repeats - this guarantee that one press OK will
|
||||
// be guarantee send the required minimum protocol data packets
|
||||
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
subghz_block_generic_global.endless_tx = true;
|
||||
if(!subghz_tx_start(
|
||||
subghz,
|
||||
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen))) {
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
subghz_txrx_hopper_unpause(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
} else {
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) {
|
||||
//CC1101 Stop Tx -> Start RX
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
//CC1101 Stop Tx -> next tick event Start RX
|
||||
// user release OK
|
||||
// we switch off endless_tx - that mean protocols yield finish endless transmission,
|
||||
// send upload "repeat=xx" times, and after will be stoped by the tick event down in this code
|
||||
subghz->state_notifications = SubGhzNotificationStateTxWait;
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
subghz_scene_receiver_info_draw_widget(subghz);
|
||||
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) {
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
|
||||
subghz_txrx_hopper_unpause(subghz->txrx);
|
||||
if(!subghz_history_full(subghz->history)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) {
|
||||
//CC1101 Stop RX -> Save
|
||||
@@ -218,6 +222,27 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
notification_message(subghz->notifications, &sequence_blink_green_100);
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
break;
|
||||
case SubGhzNotificationStateTxWait:
|
||||
// we wait until hardware TX finished and after stop TX and start RX, else just blink led
|
||||
if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
} else {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
// update screen
|
||||
widget_reset(subghz->widget);
|
||||
subghz_scene_receiver_info_draw_widget(subghz);
|
||||
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
|
||||
if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) {
|
||||
subghz_txrx_rx_start(subghz->txrx);
|
||||
subghz_txrx_hopper_unpause(subghz->txrx);
|
||||
if(!subghz_history_full(subghz->history)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateRx;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "../subghz_i.h"
|
||||
|
||||
#include <lib/subghz/blocks/custom_btn.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include "applications/main/subghz/helpers/subghz_txrx_i.h"
|
||||
|
||||
typedef enum {
|
||||
SubGhzRpcStateIdle,
|
||||
@@ -52,9 +54,13 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcButtonPress) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateLoaded) {
|
||||
// START endless TX until user release button
|
||||
// variable used in protocol yield for endless TX
|
||||
subghz_block_generic_global.endless_tx = true;
|
||||
switch(
|
||||
subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
|
||||
case SubGhzTxRxStartTxStateErrorOnlyRx:
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock);
|
||||
rpc_system_app_set_error_text(
|
||||
@@ -62,6 +68,7 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
"Transmission on this frequency is restricted in your settings");
|
||||
break;
|
||||
case SubGhzTxRxStartTxStateErrorParserOthers:
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
rpc_system_app_set_error_text(
|
||||
@@ -80,50 +87,53 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcButtonRelease) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateTx) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_blink_stop(subghz);
|
||||
// user release button
|
||||
// set endless TX to OFF and switch off TX in section event.type == SceneManagerEventTypeTick
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
result = true;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
// scene_manager_set_scene_state(
|
||||
// subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateLoaded) {
|
||||
switch(
|
||||
subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
|
||||
case SubGhzTxRxStartTxStateErrorOnlyRx:
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock);
|
||||
rpc_system_app_set_error_text(
|
||||
subghz->rpc_ctx,
|
||||
"Transmission on this frequency is restricted in your region");
|
||||
break;
|
||||
case SubGhzTxRxStartTxStateErrorParserOthers:
|
||||
rpc_system_app_set_error_code(
|
||||
subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
rpc_system_app_set_error_text(
|
||||
subghz->rpc_ctx, "Error in protocol parameters description");
|
||||
break;
|
||||
|
||||
default: //if(SubGhzTxRxStartTxStateOk)
|
||||
result = true;
|
||||
subghz_blink_start(subghz);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// USELESS PART
|
||||
// } else if(event.event == SubGhzCustomEventSceneRpcButtonPressRelease) {
|
||||
// bool result = false;
|
||||
// if(state == SubGhzRpcStateLoaded) {
|
||||
// switch(
|
||||
// subghz_txrx_tx_start(subghz->txrx, subghz_txrx_get_fff_data(subghz->txrx))) {
|
||||
// case SubGhzTxRxStartTxStateErrorOnlyRx:
|
||||
// rpc_system_app_set_error_code(
|
||||
// subghz->rpc_ctx, RpcAppSystemErrorCodeRegionLock);
|
||||
// rpc_system_app_set_error_text(
|
||||
// subghz->rpc_ctx,
|
||||
// "Transmission on this frequency is restricted in your region");
|
||||
// break;
|
||||
// case SubGhzTxRxStartTxStateErrorParserOthers:
|
||||
// rpc_system_app_set_error_code(
|
||||
// subghz->rpc_ctx, RpcAppSystemErrorCodeInternalParse);
|
||||
// rpc_system_app_set_error_text(
|
||||
// subghz->rpc_ctx, "Error in protocol parameters description");
|
||||
// break;
|
||||
|
||||
// Stop transmission
|
||||
if(state == SubGhzRpcStateTx) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_blink_stop(subghz);
|
||||
result = true;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
// default: //if(SubGhzTxRxStartTxStateOk)
|
||||
// result = true;
|
||||
// subghz_blink_start(subghz);
|
||||
// scene_manager_set_scene_state(
|
||||
// subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateTx);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Stop transmission
|
||||
// if(state == SubGhzRpcStateTx) {
|
||||
// subghz_txrx_stop(subghz->txrx);
|
||||
// subghz_blink_stop(subghz);
|
||||
// result = true;
|
||||
// }
|
||||
// scene_manager_set_scene_state(
|
||||
// subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
// rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
} else if(event.event == SubGhzCustomEventSceneRpcLoad) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateIdle) {
|
||||
@@ -139,6 +149,19 @@ bool subghz_scene_rpc_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
// if hardware TX finished then stop TX correctly
|
||||
if(subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
|
||||
bool result = false;
|
||||
if(state == SubGhzRpcStateTx) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_blink_stop(subghz);
|
||||
result = true;
|
||||
}
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneRpc, SubGhzRpcStateIdle);
|
||||
rpc_system_app_confirm(subghz->rpc_ctx, result);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
@@ -25,11 +25,17 @@ void subghz_scene_save_name_on_enter(void* context) {
|
||||
char file_name_buf[SUBGHZ_MAX_LEN_NAME] = {0};
|
||||
DateTime* datetime = subghz->save_datetime_set ? &subghz->save_datetime : NULL;
|
||||
subghz->save_datetime_set = false;
|
||||
#ifdef SUBGHZ_ADD_MANUALLY
|
||||
name_generator_make_auto_datetime(
|
||||
file_name_buf, SUBGHZ_MAX_LEN_NAME, SUBGHZ_APP_FILENAME_PREFIX, datetime);
|
||||
furi_string_set(file_name, file_name_buf);
|
||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||
dev_name_empty = true;
|
||||
#else
|
||||
if(!subghz_path_is_file(subghz->file_path)) {
|
||||
SubGhzProtocolDecoderBase* decoder_result = subghz_txrx_get_decoder(subghz->txrx);
|
||||
if(subghz->last_settings->protocol_file_names && decoder_result != NULL &&
|
||||
strlen(decoder_result->protocol->name) != 0 &&
|
||||
!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSetType)) {
|
||||
strlen(decoder_result->protocol->name) != 0) {
|
||||
name_generator_make_auto_datetime(
|
||||
file_name_buf, SUBGHZ_MAX_LEN_NAME, decoder_result->protocol->name, datetime);
|
||||
} else {
|
||||
@@ -62,6 +68,7 @@ void subghz_scene_save_name_on_enter(void* context) {
|
||||
}
|
||||
furi_string_set(subghz->file_path, dir_name);
|
||||
}
|
||||
#endif
|
||||
|
||||
strlcpy(subghz->file_name_tmp, furi_string_get_cstr(file_name), SUBGHZ_MAX_LEN_NAME);
|
||||
text_input_set_header_text(text_input, "Name signal");
|
||||
@@ -89,6 +96,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
// Set file path to default
|
||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||
//
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
if(!(strcmp(subghz->file_name_tmp, "") == 0) ||
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubGhzCustomEventManagerNoSet) {
|
||||
@@ -96,6 +104,7 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
furi_string_set(subghz->file_path, subghz->file_path_tmp);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
|
||||
@@ -113,24 +122,26 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) !=
|
||||
SubGhzCustomEventManagerNoSet) {
|
||||
subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_txrx_get_fff_data(subghz->txrx),
|
||||
furi_string_get_cstr(subghz->file_path));
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneSetType,
|
||||
SubGhzCustomEventManagerNoSet);
|
||||
} else {
|
||||
subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen),
|
||||
furi_string_get_cstr(subghz->file_path));
|
||||
#ifdef SUBGHZ_ADD_MANUALLY
|
||||
if(!subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_txrx_get_fff_data(subghz->txrx),
|
||||
furi_string_get_cstr(subghz->file_path))) {
|
||||
return true;
|
||||
}
|
||||
#else
|
||||
if(!subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_history_get_raw_data(subghz->history, subghz->idx_menu_chosen),
|
||||
furi_string_get_cstr(subghz->file_path))) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef SUBGHZ_ADD_MANUALLY
|
||||
subghz_file_name_clear(subghz);
|
||||
#else
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubGhzCustomEventManagerNoSet) {
|
||||
subghz_protocol_raw_gen_fff_data(
|
||||
@@ -142,19 +153,21 @@ bool subghz_scene_save_name_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
subghz_file_name_clear(subghz);
|
||||
}
|
||||
#endif
|
||||
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveSuccess);
|
||||
#ifdef SUBGHZ_ADD_MANUALLY
|
||||
dolphin_deed(DolphinDeedSubGhzAddManually);
|
||||
#else
|
||||
if(scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneSavedMenu)) {
|
||||
// Nothing, do not count editing as saving
|
||||
} else if(scene_manager_has_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneMoreRAW)) {
|
||||
// Ditto, for RAW signals
|
||||
} else if(scene_manager_has_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneSetType)) {
|
||||
dolphin_deed(DolphinDeedSubGhzAddManually);
|
||||
} else {
|
||||
dolphin_deed(DolphinDeedSubGhzSave);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
} else {
|
||||
furi_string_set(subghz->error_str, "No name file");
|
||||
|
||||
@@ -24,6 +24,12 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event)
|
||||
SubGhz* subghz = context;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubGhzCustomEventSceneSaveSuccess) {
|
||||
#ifdef SUBGHZ_ADD_MANUALLY
|
||||
while(scene_manager_previous_scene(subghz->scene_manager))
|
||||
;
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
#else
|
||||
if(!scene_manager_has_previous_scene(subghz->scene_manager, SubGhzSceneDecodeRAW)) {
|
||||
if(!scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneReceiver)) {
|
||||
@@ -59,6 +65,7 @@ bool subghz_scene_save_success_on_event(void* context, SceneManagerEvent event)
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,30 +69,21 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSavedMenu, event.event);
|
||||
if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexDelete) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexDelete);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneDelete);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexEdit) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEdit);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexGeo) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexGeo);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneShowGps, true);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowGps);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexSignalSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexSignalSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSignalSettings);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ void subghz_scene_set_button_on_enter(void* context) {
|
||||
byte_ptr = &subghz->gen_info->keeloq.btn;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq.btn);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
byte_ptr = &subghz->gen_info->keeloq_bft.btn;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_bft.btn);
|
||||
case GenKeeloqSeed:
|
||||
byte_ptr = &subghz->gen_info->keeloq_seed.btn;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_seed.btn);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
byte_ptr = &subghz->gen_info->alutech_at_4n.btn;
|
||||
@@ -36,6 +36,10 @@ void subghz_scene_set_button_on_enter(void* context) {
|
||||
byte_ptr = &subghz->gen_info->somfy_telis.btn;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_telis.btn);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
byte_ptr = &subghz->gen_info->somfy_keytis.btn;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_keytis.btn);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
byte_ptr = &subghz->gen_info->kinggates_stylo_4k.btn;
|
||||
byte_count = sizeof(subghz->gen_info->kinggates_stylo_4k.btn);
|
||||
@@ -44,6 +48,14 @@ void subghz_scene_set_button_on_enter(void* context) {
|
||||
byte_ptr = &subghz->gen_info->beninca_arc.btn;
|
||||
byte_count = sizeof(subghz->gen_info->beninca_arc.btn);
|
||||
break;
|
||||
case GenJarolift:
|
||||
byte_ptr = &subghz->gen_info->jarolift.btn;
|
||||
byte_count = sizeof(subghz->gen_info->jarolift.btn);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
byte_ptr = &subghz->gen_info->ditec_gol4.btn;
|
||||
byte_count = sizeof(subghz->gen_info->ditec_gol4.btn);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
byte_ptr = &subghz->gen_info->nice_flor_s.btn;
|
||||
byte_count = sizeof(subghz->gen_info->nice_flor_s.btn);
|
||||
@@ -87,12 +99,15 @@ bool subghz_scene_set_button_on_event(void* context, SceneManagerEvent event) {
|
||||
switch(subghz->gen_info->type) {
|
||||
case GenFaacSLH:
|
||||
case GenKeeloq:
|
||||
case GenKeeloqBFT:
|
||||
case GenKeeloqSeed:
|
||||
case GenAlutechAt4n:
|
||||
case GenSomfyTelis:
|
||||
case GenKingGatesStylo4k:
|
||||
case GenBenincaARC:
|
||||
case GenJarolift:
|
||||
case GenDitecGOL4:
|
||||
case GenNiceFlorS:
|
||||
case GenSomfyKeytis:
|
||||
case GenSecPlus2:
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetCounter);
|
||||
break;
|
||||
|
||||
@@ -30,9 +30,9 @@ void subghz_scene_set_counter_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->came_atomo.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->came_atomo.cnt);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_bft.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_bft.cnt);
|
||||
case GenKeeloqSeed:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_seed.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_seed.cnt);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->alutech_at_4n.cnt;
|
||||
@@ -42,6 +42,10 @@ void subghz_scene_set_counter_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->somfy_telis.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_telis.cnt);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->somfy_keytis.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_keytis.cnt);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->kinggates_stylo_4k.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->kinggates_stylo_4k.cnt);
|
||||
@@ -50,6 +54,14 @@ void subghz_scene_set_counter_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->beninca_arc.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->beninca_arc.cnt);
|
||||
break;
|
||||
case GenJarolift:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->jarolift.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->jarolift.cnt);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->ditec_gol4.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->ditec_gol4.cnt);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.cnt;
|
||||
byte_count = sizeof(subghz->gen_info->nice_flor_s.cnt);
|
||||
@@ -111,8 +123,8 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
case GenCameAtomo:
|
||||
subghz->gen_info->came_atomo.cnt = __bswap16(subghz->gen_info->came_atomo.cnt);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
subghz->gen_info->keeloq_bft.cnt = __bswap16(subghz->gen_info->keeloq_bft.cnt);
|
||||
case GenKeeloqSeed:
|
||||
subghz->gen_info->keeloq_seed.cnt = __bswap16(subghz->gen_info->keeloq_seed.cnt);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
subghz->gen_info->alutech_at_4n.cnt =
|
||||
@@ -121,6 +133,9 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
case GenSomfyTelis:
|
||||
subghz->gen_info->somfy_telis.cnt = __bswap16(subghz->gen_info->somfy_telis.cnt);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
subghz->gen_info->somfy_keytis.cnt = __bswap16(subghz->gen_info->somfy_keytis.cnt);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
subghz->gen_info->kinggates_stylo_4k.cnt =
|
||||
__bswap16(subghz->gen_info->kinggates_stylo_4k.cnt);
|
||||
@@ -128,6 +143,12 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
case GenBenincaARC:
|
||||
subghz->gen_info->beninca_arc.cnt = __bswap32(subghz->gen_info->beninca_arc.cnt);
|
||||
break;
|
||||
case GenJarolift:
|
||||
subghz->gen_info->jarolift.cnt = __bswap16(subghz->gen_info->jarolift.cnt);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
subghz->gen_info->ditec_gol4.cnt = __bswap16(subghz->gen_info->ditec_gol4.cnt);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
subghz->gen_info->nice_flor_s.cnt = __bswap16(subghz->gen_info->nice_flor_s.cnt);
|
||||
break;
|
||||
@@ -147,7 +168,7 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
|
||||
switch(subghz->gen_info->type) {
|
||||
case GenFaacSLH:
|
||||
case GenKeeloqBFT:
|
||||
case GenKeeloqSeed:
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetSeed);
|
||||
return true;
|
||||
case GenKeeloq:
|
||||
@@ -186,6 +207,15 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->gen_info->somfy_telis.btn,
|
||||
subghz->gen_info->somfy_telis.cnt);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
generated_protocol = subghz_txrx_gen_somfy_keytis_protocol(
|
||||
subghz->txrx,
|
||||
subghz->gen_info->mod,
|
||||
subghz->gen_info->freq,
|
||||
subghz->gen_info->somfy_keytis.serial,
|
||||
subghz->gen_info->somfy_keytis.btn,
|
||||
subghz->gen_info->somfy_keytis.cnt);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
generated_protocol = subghz_txrx_gen_kinggates_stylo_4k_protocol(
|
||||
subghz->txrx,
|
||||
@@ -204,6 +234,24 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->gen_info->beninca_arc.btn,
|
||||
subghz->gen_info->beninca_arc.cnt);
|
||||
break;
|
||||
case GenJarolift:
|
||||
generated_protocol = subghz_txrx_gen_jarolift_protocol(
|
||||
subghz->txrx,
|
||||
subghz->gen_info->mod,
|
||||
subghz->gen_info->freq,
|
||||
subghz->gen_info->jarolift.serial,
|
||||
subghz->gen_info->jarolift.btn,
|
||||
subghz->gen_info->jarolift.cnt);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
generated_protocol = subghz_txrx_gen_ditec_gol4_protocol(
|
||||
subghz->txrx,
|
||||
subghz->gen_info->mod,
|
||||
subghz->gen_info->freq,
|
||||
subghz->gen_info->ditec_gol4.serial,
|
||||
subghz->gen_info->ditec_gol4.btn,
|
||||
subghz->gen_info->ditec_gol4.cnt);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
generated_protocol = subghz_txrx_gen_nice_flor_s_protocol(
|
||||
subghz->txrx,
|
||||
@@ -248,8 +296,6 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
subghz_file_name_clear(subghz);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +76,6 @@ bool subghz_scene_set_key_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
subghz_file_name_clear(subghz);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,16 +22,19 @@ void subghz_scene_set_seed_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->faac_slh.seed;
|
||||
byte_count = sizeof(subghz->gen_info->faac_slh.seed);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_bft.seed;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_bft.seed);
|
||||
case GenKeeloqSeed:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_seed.seed;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_seed.seed);
|
||||
break;
|
||||
// Not needed for these types
|
||||
case GenKeeloq:
|
||||
case GenAlutechAt4n:
|
||||
case GenSomfyTelis:
|
||||
case GenSomfyKeytis:
|
||||
case GenKingGatesStylo4k:
|
||||
case GenBenincaARC:
|
||||
case GenJarolift:
|
||||
case GenDitecGOL4:
|
||||
case GenNiceFlorS:
|
||||
case GenSecPlus2:
|
||||
case GenPhoenixV2:
|
||||
@@ -75,24 +78,27 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->gen_info->faac_slh.seed,
|
||||
subghz->gen_info->faac_slh.manuf);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
subghz->gen_info->keeloq_bft.seed = __bswap32(subghz->gen_info->keeloq_bft.seed);
|
||||
generated_protocol = subghz_txrx_gen_keeloq_bft_protocol(
|
||||
case GenKeeloqSeed:
|
||||
subghz->gen_info->keeloq_seed.seed = __bswap32(subghz->gen_info->keeloq_seed.seed);
|
||||
generated_protocol = subghz_txrx_gen_keeloq_seed_protocol(
|
||||
subghz->txrx,
|
||||
subghz->gen_info->mod,
|
||||
subghz->gen_info->freq,
|
||||
subghz->gen_info->keeloq_bft.serial,
|
||||
subghz->gen_info->keeloq_bft.btn,
|
||||
subghz->gen_info->keeloq_bft.cnt,
|
||||
subghz->gen_info->keeloq_bft.seed,
|
||||
subghz->gen_info->keeloq_bft.manuf);
|
||||
subghz->gen_info->keeloq_seed.serial,
|
||||
subghz->gen_info->keeloq_seed.btn,
|
||||
subghz->gen_info->keeloq_seed.cnt,
|
||||
subghz->gen_info->keeloq_seed.seed,
|
||||
subghz->gen_info->keeloq_seed.manuf);
|
||||
break;
|
||||
// Not needed for these types
|
||||
case GenKeeloq:
|
||||
case GenAlutechAt4n:
|
||||
case GenSomfyTelis:
|
||||
case GenSomfyKeytis:
|
||||
case GenKingGatesStylo4k:
|
||||
case GenBenincaARC:
|
||||
case GenJarolift:
|
||||
case GenDitecGOL4:
|
||||
case GenNiceFlorS:
|
||||
case GenSecPlus2:
|
||||
case GenPhoenixV2:
|
||||
@@ -113,8 +119,6 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) {
|
||||
} else {
|
||||
subghz_file_name_clear(subghz);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ void subghz_scene_set_serial_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->came_atomo.serial;
|
||||
byte_count = sizeof(subghz->gen_info->came_atomo.serial);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_bft.serial;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_bft.serial);
|
||||
case GenKeeloqSeed:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->keeloq_seed.serial;
|
||||
byte_count = sizeof(subghz->gen_info->keeloq_seed.serial);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->alutech_at_4n.serial;
|
||||
@@ -42,6 +42,10 @@ void subghz_scene_set_serial_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->somfy_telis.serial;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_telis.serial);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->somfy_keytis.serial;
|
||||
byte_count = sizeof(subghz->gen_info->somfy_keytis.serial);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->kinggates_stylo_4k.serial;
|
||||
byte_count = sizeof(subghz->gen_info->kinggates_stylo_4k.serial);
|
||||
@@ -50,6 +54,14 @@ void subghz_scene_set_serial_on_enter(void* context) {
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->beninca_arc.serial;
|
||||
byte_count = sizeof(subghz->gen_info->beninca_arc.serial);
|
||||
break;
|
||||
case GenJarolift:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->jarolift.serial;
|
||||
byte_count = sizeof(subghz->gen_info->jarolift.serial);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->ditec_gol4.serial;
|
||||
byte_count = sizeof(subghz->gen_info->ditec_gol4.serial);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.serial;
|
||||
byte_count = sizeof(subghz->gen_info->nice_flor_s.serial);
|
||||
@@ -106,9 +118,9 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->gen_info->came_atomo.serial =
|
||||
__bswap32(subghz->gen_info->came_atomo.serial);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
subghz->gen_info->keeloq_bft.serial =
|
||||
__bswap32(subghz->gen_info->keeloq_bft.serial);
|
||||
case GenKeeloqSeed:
|
||||
subghz->gen_info->keeloq_seed.serial =
|
||||
__bswap32(subghz->gen_info->keeloq_seed.serial);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
subghz->gen_info->alutech_at_4n.serial =
|
||||
@@ -118,10 +130,21 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->gen_info->somfy_telis.serial =
|
||||
__bswap32(subghz->gen_info->somfy_telis.serial);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
subghz->gen_info->somfy_keytis.serial =
|
||||
__bswap32(subghz->gen_info->somfy_keytis.serial);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
subghz->gen_info->kinggates_stylo_4k.serial =
|
||||
__bswap32(subghz->gen_info->kinggates_stylo_4k.serial);
|
||||
break;
|
||||
case GenJarolift:
|
||||
subghz->gen_info->jarolift.serial = __bswap32(subghz->gen_info->jarolift.serial);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
subghz->gen_info->ditec_gol4.serial =
|
||||
__bswap32(subghz->gen_info->ditec_gol4.serial);
|
||||
break;
|
||||
case GenBenincaARC:
|
||||
subghz->gen_info->beninca_arc.serial =
|
||||
__bswap32(subghz->gen_info->beninca_arc.serial);
|
||||
@@ -149,11 +172,14 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) {
|
||||
switch(subghz->gen_info->type) {
|
||||
case GenFaacSLH:
|
||||
case GenKeeloq:
|
||||
case GenKeeloqBFT:
|
||||
case GenKeeloqSeed:
|
||||
case GenAlutechAt4n:
|
||||
case GenSomfyTelis:
|
||||
case GenSomfyKeytis:
|
||||
case GenKingGatesStylo4k:
|
||||
case GenBenincaARC:
|
||||
case GenJarolift:
|
||||
case GenDitecGOL4:
|
||||
case GenNiceFlorS:
|
||||
case GenSecPlus2:
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetButton);
|
||||
|
||||
@@ -15,13 +15,17 @@ static const char* submenu_names[SetTypeMAX] = {
|
||||
[SetTypeFaacSLH_868] = "FAAC SLH 868MHz",
|
||||
[SetTypeFaacSLH_433] = "FAAC SLH 433MHz",
|
||||
[SetTypeBFTMitto] = "BFT Mitto 433MHz",
|
||||
[SetTypeErreka433] = "Erreka 433MHz",
|
||||
[SetTypeSomfyTelis] = "Somfy Telis 433MHz",
|
||||
[SetTypeSomfyKeytis] = "Somfy Keytis 433MHz",
|
||||
[SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz",
|
||||
[SetTypeAlutechAT4N] = "Alutech AT4N 433MHz",
|
||||
[SetTypeRoger_433] = "Roger 433MHz",
|
||||
[SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz",
|
||||
[SetTypeKingGatesStylo4k] = "KingGates Stylo4k 433M.",
|
||||
[SetTypeBenincaARC] = "Beninca ARC 433MHz",
|
||||
[SetTypeJarolift] = "Jarolift 433MHz",
|
||||
[SetTypeDitecGOL4] = "Ditec GOL4 433MHz",
|
||||
[SetTypeHCS101_433_92] = "KL: HCS101 433MHz",
|
||||
[SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz",
|
||||
[SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz",
|
||||
@@ -53,9 +57,22 @@ static const char* submenu_names[SetTypeMAX] = {
|
||||
[SetTypeNovoferm_433_92] = "KL: Novoferm 433MHz",
|
||||
[SetTypeHormannEcoStar_433_92] = "KL: Hor. EcoStar 433MHz",
|
||||
[SetTypeCardinS449_433FM] = "KL: Cardin S449 433MHz",
|
||||
[SetTypePujol433] = "KL: Pujol 433MHz",
|
||||
[SetTypePujol_Vario433] = "KL: Pujol Vario 433MHz",
|
||||
[SetTypeET_Blue433] = "KL: ET Blue 433MHz",
|
||||
[SetTypeET_Blue_Mix433] = "KL: ET Blue Mix 433MHz",
|
||||
[SetTypeATA_PTX4_433] = "KL: ATA PTX4 433MHz",
|
||||
[SetTypeSeav433] = "KL: Seav 433MHz",
|
||||
[SetTypeWisniowski433] = "KL: Wisniowski 433MHz",
|
||||
[SetTypeFadini433] = "KL: Fadini 433MHz",
|
||||
[SetTypeMc_Garcia433] = "KL: Mc Garcia 433MHz",
|
||||
[SetTypeClemsa_Mutancode433] = "KL: Clm.Mutancode 433M.",
|
||||
[SetTypeDoormatic433] = "KL: Doormatic 433MHz",
|
||||
[SetTypeElvox433] = "KL: Elvox 433MHz",
|
||||
[SetTypeVerex433] = "KL: Verex 433MHz",
|
||||
[SetTypeFAACRCXT_433_92] = "KL: FAAC RC,XT 433MHz",
|
||||
[SetTypeFAACRCXT_868] = "KL: FAAC RC,XT 868MHz",
|
||||
[SetTypeGeniusBravo433] = "KL: Genius Bravo 433MHz",
|
||||
[SetTypeGeniusBravo433] = "KL: Genius TX4RC 433M.",
|
||||
[SetTypeNiceMHouse_433_92] = "KL: Mhouse 433MHz",
|
||||
[SetTypeNiceSmilo_433_92] = "KL: Nice Smilo 433MHz",
|
||||
[SetTypeNiceFlorS_433_92] = "Nice FloR-S 433MHz",
|
||||
@@ -101,9 +118,6 @@ void subghz_scene_set_type_on_enter(void* context) {
|
||||
subghz->submenu, submenu_names[i], i, subghz_scene_set_type_submenu_callback, subghz);
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
@@ -160,16 +174,16 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) {
|
||||
gen_info.came_atomo.serial,
|
||||
gen_info.came_atomo.cnt);
|
||||
break;
|
||||
case GenKeeloqBFT:
|
||||
generated_protocol = subghz_txrx_gen_keeloq_bft_protocol(
|
||||
case GenKeeloqSeed:
|
||||
generated_protocol = subghz_txrx_gen_keeloq_seed_protocol(
|
||||
subghz->txrx,
|
||||
gen_info.mod,
|
||||
gen_info.freq,
|
||||
gen_info.keeloq_bft.serial,
|
||||
gen_info.keeloq_bft.btn,
|
||||
gen_info.keeloq_bft.cnt,
|
||||
gen_info.keeloq_bft.seed,
|
||||
gen_info.keeloq_bft.manuf);
|
||||
gen_info.keeloq_seed.serial,
|
||||
gen_info.keeloq_seed.btn,
|
||||
gen_info.keeloq_seed.cnt,
|
||||
gen_info.keeloq_seed.seed,
|
||||
gen_info.keeloq_seed.manuf);
|
||||
break;
|
||||
case GenAlutechAt4n:
|
||||
generated_protocol = subghz_txrx_gen_alutech_at_4n_protocol(
|
||||
@@ -189,6 +203,15 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) {
|
||||
gen_info.somfy_telis.btn,
|
||||
gen_info.somfy_telis.cnt);
|
||||
break;
|
||||
case GenSomfyKeytis:
|
||||
generated_protocol = subghz_txrx_gen_somfy_keytis_protocol(
|
||||
subghz->txrx,
|
||||
gen_info.mod,
|
||||
gen_info.freq,
|
||||
gen_info.somfy_keytis.serial,
|
||||
gen_info.somfy_keytis.btn,
|
||||
gen_info.somfy_keytis.cnt);
|
||||
break;
|
||||
case GenKingGatesStylo4k:
|
||||
generated_protocol = subghz_txrx_gen_kinggates_stylo_4k_protocol(
|
||||
subghz->txrx,
|
||||
@@ -207,6 +230,24 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) {
|
||||
gen_info.beninca_arc.btn,
|
||||
gen_info.beninca_arc.cnt);
|
||||
break;
|
||||
case GenJarolift:
|
||||
generated_protocol = subghz_txrx_gen_jarolift_protocol(
|
||||
subghz->txrx,
|
||||
gen_info.mod,
|
||||
gen_info.freq,
|
||||
gen_info.jarolift.serial,
|
||||
gen_info.jarolift.btn,
|
||||
gen_info.jarolift.cnt);
|
||||
break;
|
||||
case GenDitecGOL4:
|
||||
generated_protocol = subghz_txrx_gen_ditec_gol4_protocol(
|
||||
subghz->txrx,
|
||||
gen_info.mod,
|
||||
gen_info.freq,
|
||||
gen_info.ditec_gol4.serial,
|
||||
gen_info.ditec_gol4.btn,
|
||||
gen_info.ditec_gol4.cnt);
|
||||
break;
|
||||
case GenNiceFlorS:
|
||||
generated_protocol = subghz_txrx_gen_nice_flor_s_protocol(
|
||||
subghz->txrx,
|
||||
@@ -245,8 +286,6 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) {
|
||||
|
||||
if(generated_protocol) {
|
||||
subghz_file_name_clear(subghz);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSetType, SubGhzCustomEventManagerSet);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaveName);
|
||||
} else {
|
||||
furi_string_set(subghz->error_str, "Function requires\nan SD card with\nfresh databases.");
|
||||
@@ -263,16 +302,15 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event >= SetTypeMAX) {
|
||||
return false;
|
||||
}
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneSetType, event.event);
|
||||
|
||||
subghz_gen_info_reset(subghz->gen_info);
|
||||
subghz_scene_set_type_fill_generation_infos(subghz->gen_info, event.event);
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart) ==
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) ==
|
||||
SubmenuIndexAddManually) {
|
||||
generated_protocol = subghz_scene_set_type_generate_protocol_from_infos(subghz);
|
||||
} else if(
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart) ==
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneSetType) ==
|
||||
SubmenuIndexAddManuallyAdvanced) {
|
||||
switch(subghz->gen_info->type) {
|
||||
case GenData: // Key (u64)
|
||||
@@ -283,11 +321,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) {
|
||||
case GenFaacSLH: // Serial (u32), Button (u8), Counter (u32), Seed (u32)
|
||||
case GenKeeloq: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenCameAtomo: // Serial (u32), Counter (u16)
|
||||
case GenKeeloqBFT: // Serial (u32), Button (u8), Counter (u16), Seed (u32)
|
||||
case GenKeeloqSeed: // Serial (u32), Button (u8), Counter (u16), Seed (u32)
|
||||
case GenAlutechAt4n: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenSomfyTelis: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenSomfyKeytis: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenKingGatesStylo4k: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenBenincaARC: // Serial (u32), Button (u8), Counter (u32)
|
||||
case GenJarolift: // Serial (u32), Button (u4), Counter (u16)
|
||||
case GenDitecGOL4: // Serial (u32), Button (u4), Counter (u16)
|
||||
case GenNiceFlorS: // Serial (u32), Button (u8), Counter (u16)
|
||||
case GenSecPlus2: // Serial (u32), Button (u8), Counter (u32)
|
||||
case GenPhoenixV2: // Serial (u32), Counter (u16)
|
||||
|
||||
@@ -11,11 +11,18 @@
|
||||
static uint32_t counter_mode = 0xff;
|
||||
static uint32_t counter32 = 0x0;
|
||||
static uint16_t counter16 = 0x0;
|
||||
static uint8_t byte_count = 0;
|
||||
static uint8_t* byte_ptr = NULL;
|
||||
static uint8_t cnt_byte_count = 0;
|
||||
static uint8_t* cnt_byte_ptr = NULL;
|
||||
|
||||
static FuriString* byte_input_text;
|
||||
|
||||
#define COUNTER_MODE_COUNT 7
|
||||
static uint8_t button = 0x0;
|
||||
static uint8_t btn_byte_count = 1;
|
||||
static uint8_t* btn_byte_ptr = NULL;
|
||||
|
||||
static uint8_t submenu_called = 0;
|
||||
|
||||
#define COUNTER_MODE_COUNT 8
|
||||
static const char* const counter_mode_text[COUNTER_MODE_COUNT] = {
|
||||
"System",
|
||||
"Mode 1",
|
||||
@@ -24,6 +31,7 @@ static const char* const counter_mode_text[COUNTER_MODE_COUNT] = {
|
||||
"Mode 4",
|
||||
"Mode 5",
|
||||
"Mode 6",
|
||||
"Mode 7",
|
||||
};
|
||||
|
||||
static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = {
|
||||
@@ -34,6 +42,7 @@ static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = {
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -42,11 +51,12 @@ typedef struct {
|
||||
} Protocols;
|
||||
|
||||
// List of protocols names and appropriate CounterMode counts
|
||||
static Protocols protocols[] = {
|
||||
static const Protocols protocols[] = {
|
||||
{"Nice FloR-S", 3},
|
||||
{"CAME Atomo", 4},
|
||||
{"Alutech AT-4N", 3},
|
||||
{"KeeLoq", 7},
|
||||
{"KeeLoq", 8},
|
||||
{"Phoenix_V2", 3},
|
||||
};
|
||||
|
||||
#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols));
|
||||
@@ -55,6 +65,38 @@ void subghz_scene_signal_settings_counter_mode_changed(VariableItem* item) {
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, counter_mode_text[index]);
|
||||
counter_mode = counter_mode_value[index];
|
||||
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
const char* file_path = furi_string_get_cstr(subghz->file_path);
|
||||
|
||||
furi_assert(subghz);
|
||||
furi_assert(file_path);
|
||||
|
||||
// update file every time when we change mode
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
|
||||
// check is the file available for update/insert CounterMode value
|
||||
if(flipper_format_file_open_existing(fff_data_file, file_path)) {
|
||||
if(flipper_format_insert_or_update_uint32(fff_data_file, "CounterMode", &counter_mode, 1)) {
|
||||
FURI_LOG_D(TAG, "Successfully updated/inserted CounterMode value %li", counter_mode);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error update/insert CounterMode value");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error open file %s for writing", file_path);
|
||||
}
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// we need to reload file after editing it
|
||||
if(subghz_key_load(subghz, file_path, false)) {
|
||||
FURI_LOG_D(TAG, "Subghz file was successfully reloaded");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error reloading subghz file");
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_scene_signal_settings_byte_input_callback(void* context) {
|
||||
@@ -67,6 +109,7 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex
|
||||
|
||||
// when we click OK on "Edit counter" item
|
||||
if(index == 1) {
|
||||
submenu_called = 1;
|
||||
furi_string_cat_printf(byte_input_text, "%i", subghz_block_generic_global.cnt_length_bit);
|
||||
furi_string_cat_str(byte_input_text, "-bits counter in HEX");
|
||||
|
||||
@@ -79,8 +122,27 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex
|
||||
subghz_scene_signal_settings_byte_input_callback,
|
||||
NULL,
|
||||
subghz,
|
||||
byte_ptr,
|
||||
byte_count);
|
||||
cnt_byte_ptr,
|
||||
cnt_byte_count);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput);
|
||||
}
|
||||
// when we click OK on "Edit button" item
|
||||
if(index == 2) {
|
||||
submenu_called = 2;
|
||||
furi_string_cat_printf(byte_input_text, "%i", subghz_block_generic_global.btn_length_bit);
|
||||
furi_string_cat_str(byte_input_text, "-bits button in HEX");
|
||||
|
||||
// Setup byte_input view
|
||||
ByteInput* byte_input = subghz->byte_input;
|
||||
byte_input_set_header_text(byte_input, furi_string_get_cstr(byte_input_text));
|
||||
|
||||
byte_input_set_result_callback(
|
||||
byte_input,
|
||||
subghz_scene_signal_settings_byte_input_callback,
|
||||
NULL,
|
||||
subghz,
|
||||
btn_byte_ptr,
|
||||
btn_byte_count);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput);
|
||||
}
|
||||
}
|
||||
@@ -130,44 +192,9 @@ void subghz_scene_signal_settings_on_enter(void* context) {
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// ### Counter edit section ###
|
||||
byte_input_text = furi_string_alloc_set_str("Enter ");
|
||||
bool counter_not_available = true;
|
||||
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
|
||||
|
||||
// deserialaze and decode loaded sugbhz file and push data to subghz_block_generic_global variable
|
||||
if(subghz_protocol_decoder_base_deserialize(decoder, subghz_txrx_get_fff_data(subghz->txrx)) ==
|
||||
SubGhzProtocolStatusOk) {
|
||||
subghz_protocol_decoder_base_get_string(decoder, tmp_text);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Cant deserialize this subghz file");
|
||||
}
|
||||
|
||||
if(!subghz_block_generic_global.cnt_is_available) {
|
||||
counter_mode = 0xff;
|
||||
FURI_LOG_D(TAG, "Counter mode and edit not available for this protocol");
|
||||
} else {
|
||||
counter_not_available = false;
|
||||
|
||||
// Check is there byte_count more than 2 hex bytes long or not
|
||||
// To show hex value we must correct revert bytes for ByteInput view with __bswapХХ
|
||||
if(subghz_block_generic_global.cnt_length_bit > 16) {
|
||||
counter32 = subghz_block_generic_global.current_cnt;
|
||||
furi_string_printf(tmp_text, "%lX", counter32);
|
||||
counter32 = __bswap32(counter32);
|
||||
byte_ptr = (uint8_t*)&counter32;
|
||||
byte_count = 4;
|
||||
} else {
|
||||
counter16 = subghz_block_generic_global.current_cnt;
|
||||
furi_string_printf(tmp_text, "%X", counter16);
|
||||
counter16 = __bswap16(counter16);
|
||||
byte_ptr = (uint8_t*)&counter16;
|
||||
byte_count = 2;
|
||||
}
|
||||
}
|
||||
|
||||
furi_assert(byte_ptr);
|
||||
furi_assert(byte_count > 0);
|
||||
bool button_not_available = true;
|
||||
|
||||
//Create and Enable/Disable variable_item_list depending on current values
|
||||
VariableItemList* variable_item_list = subghz->variable_item_list;
|
||||
@@ -186,15 +213,72 @@ void subghz_scene_signal_settings_on_enter(void* context) {
|
||||
subghz_scene_signal_settings_counter_mode_changed,
|
||||
subghz);
|
||||
value_index = value_index_int32(counter_mode, counter_mode_value, mode_count);
|
||||
|
||||
variable_item_set_current_value_index(item, value_index);
|
||||
variable_item_set_current_value_text(item, counter_mode_text[value_index]);
|
||||
variable_item_set_locked(item, (counter_mode == 0xff), "Not available\nfor this\nprotocol !");
|
||||
//
|
||||
|
||||
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
|
||||
|
||||
// deserialaze and decode loaded sugbhz file and push data to subghz_block_generic_global variable
|
||||
if(subghz_protocol_decoder_base_deserialize(decoder, subghz_txrx_get_fff_data(subghz->txrx)) ==
|
||||
SubGhzProtocolStatusOk) {
|
||||
subghz_protocol_decoder_base_get_string(decoder, tmp_text);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Cant deserialize this subghz file");
|
||||
}
|
||||
|
||||
// ### Counter edit section ###
|
||||
|
||||
if(!subghz_block_generic_global.cnt_is_available) {
|
||||
counter_mode = 0xff;
|
||||
FURI_LOG_D(TAG, "Counter mode and edit not available for this protocol");
|
||||
} else {
|
||||
counter_not_available = false;
|
||||
|
||||
// Check is there byte_count more than 2 hex bytes long or not
|
||||
// To show hex value we must correct revert bytes for ByteInput view with __bswapХХ
|
||||
if(subghz_block_generic_global.cnt_length_bit > 16) {
|
||||
counter32 = subghz_block_generic_global.current_cnt;
|
||||
furi_string_printf(tmp_text, "%lX", counter32);
|
||||
counter32 = __bswap32(counter32);
|
||||
cnt_byte_ptr = (uint8_t*)&counter32;
|
||||
cnt_byte_count = 4;
|
||||
} else {
|
||||
counter16 = subghz_block_generic_global.current_cnt;
|
||||
furi_string_printf(tmp_text, "%X", counter16);
|
||||
counter16 = __bswap16(counter16);
|
||||
cnt_byte_ptr = (uint8_t*)&counter16;
|
||||
cnt_byte_count = 2;
|
||||
}
|
||||
}
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Edit Counter", 1, NULL, subghz);
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text));
|
||||
variable_item_set_locked(item, (counter_not_available), "Not available\nfor this\nprotocol !");
|
||||
//
|
||||
|
||||
// ### Button edit section ###
|
||||
|
||||
if(!subghz_block_generic_global.btn_is_available) {
|
||||
FURI_LOG_D(TAG, "Button edit not available for this protocol");
|
||||
} else {
|
||||
button_not_available = false;
|
||||
button = subghz_block_generic_global.current_btn;
|
||||
furi_string_printf(tmp_text, "%X", button);
|
||||
btn_byte_ptr = (uint8_t*)&button;
|
||||
}
|
||||
|
||||
item = variable_item_list_add(variable_item_list, "Edit Button", 1, NULL, subghz);
|
||||
variable_item_set_current_value_index(item, 0);
|
||||
variable_item_set_current_value_text(item, furi_string_get_cstr(tmp_text));
|
||||
variable_item_set_locked(item, (button_not_available), "Not available\nfor this\nprotocol !");
|
||||
//
|
||||
|
||||
furi_assert(cnt_byte_ptr);
|
||||
furi_assert(cnt_byte_count > 0);
|
||||
furi_assert(btn_byte_ptr);
|
||||
|
||||
furi_string_free(tmp_text);
|
||||
|
||||
@@ -206,21 +290,40 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubGhzCustomEventByteInputDone) {
|
||||
switch(byte_count) {
|
||||
switch(submenu_called) {
|
||||
// edit counter
|
||||
case 1:
|
||||
switch(cnt_byte_count) {
|
||||
case 2:
|
||||
// set new cnt value and override_flag to global variable and call transmit to generate and save subghz signal
|
||||
counter16 = __bswap16(counter16);
|
||||
subghz_block_generic_global_counter_override_set(counter16);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
break;
|
||||
case 4:
|
||||
// the same for 32 bit Counter
|
||||
counter32 = __bswap32(counter32);
|
||||
subghz_block_generic_global_counter_override_set(counter32);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// edit button
|
||||
case 2:
|
||||
// set new cnt value and override_flag to global variable and call transmit to generate and save subghz signal
|
||||
counter16 = __bswap16(counter16);
|
||||
subghz_block_generic_global_counter_override_set(counter16);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
break;
|
||||
case 4:
|
||||
// the same for 32 bit Counter
|
||||
counter32 = __bswap32(counter32);
|
||||
subghz_block_generic_global_counter_override_set(counter32);
|
||||
subghz_block_generic_global_button_override_set(button);
|
||||
// save counter mult to rewrite subghz singnal without changing counter
|
||||
int32_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
// restore counter mult
|
||||
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -240,40 +343,8 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even
|
||||
|
||||
void subghz_scene_signal_settings_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
const char* file_path = furi_string_get_cstr(subghz->file_path);
|
||||
|
||||
furi_assert(subghz);
|
||||
furi_assert(file_path);
|
||||
|
||||
// if ConterMode was changed from 0xff then we must update or write new value to file
|
||||
if(counter_mode != 0xff) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
|
||||
// check is the file available for update/insert CounterMode value
|
||||
if(flipper_format_file_open_existing(fff_data_file, file_path)) {
|
||||
if(flipper_format_insert_or_update_uint32(
|
||||
fff_data_file, "CounterMode", &counter_mode, 1)) {
|
||||
FURI_LOG_D(
|
||||
TAG, "Successfully updated/inserted CounterMode value %li", counter_mode);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error update/insert CounterMode value");
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error open file %s for writing", file_path);
|
||||
}
|
||||
|
||||
flipper_format_file_close(fff_data_file);
|
||||
flipper_format_free(fff_data_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
// we need to reload file after editing when we exit from Signal Settings menu.
|
||||
if(subghz_key_load(subghz, file_path, false)) {
|
||||
FURI_LOG_D(TAG, "Subghz file was successfully reloaded");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error reloading subghz file");
|
||||
}
|
||||
}
|
||||
|
||||
// Clear views
|
||||
variable_item_list_set_selected_item(subghz->variable_item_list, 0);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "subghz_scene_start.h"
|
||||
#include <dolphin/dolphin.h>
|
||||
#include <loader/loader.h>
|
||||
|
||||
#include <lib/subghz/protocols/raw.h>
|
||||
|
||||
@@ -63,42 +64,46 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, event.event);
|
||||
if(event.event == SubmenuIndexReadRAW) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexReadRAW);
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReadRAW);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexRead) {
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexRead);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneReceiver);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexSaved) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexSaved);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexAddManually) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManually);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexAddManuallyAdvanced) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexAddManuallyAdvanced);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType);
|
||||
} else if(
|
||||
event.event == SubmenuIndexAddManually ||
|
||||
event.event == SubmenuIndexAddManuallyAdvanced) {
|
||||
const char* arg = (event.event == SubmenuIndexAddManuallyAdvanced) ?
|
||||
"AddManuallyAdvanced" :
|
||||
"AddManually";
|
||||
FuriString* self_path = furi_string_alloc();
|
||||
Loader* loader = furi_record_open(RECORD_LOADER);
|
||||
furi_check(loader_get_application_launch_path(loader, self_path));
|
||||
loader_enqueue_launch(
|
||||
loader,
|
||||
EXT_PATH("apps/assets/subghz_add_manually.fap"),
|
||||
arg,
|
||||
LoaderDeferredLaunchFlagGui);
|
||||
loader_enqueue_launch(
|
||||
loader, furi_string_get_cstr(self_path), arg, LoaderDeferredLaunchFlagGui);
|
||||
furi_record_close(RECORD_LOADER);
|
||||
furi_string_free(self_path);
|
||||
while(scene_manager_previous_scene(subghz->scene_manager))
|
||||
;
|
||||
scene_manager_stop(subghz->scene_manager);
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexFrequencyAnalyzer) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexFrequencyAnalyzer);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer);
|
||||
dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexExtSettings) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexExtSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneExtModuleSettings);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
|
||||
#include <lib/subghz/blocks/custom_btn.h>
|
||||
|
||||
#include "applications/main/subghz/helpers/subghz_txrx_i.h"
|
||||
#include "lib/subghz/blocks/generic.h"
|
||||
|
||||
#define TAG "SubGhzSceneTransmitter"
|
||||
|
||||
void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) {
|
||||
@@ -35,6 +38,7 @@ bool subghz_scene_transmitter_update_data_show(void* context) {
|
||||
furi_string_get_cstr(frequency_str),
|
||||
furi_string_get_cstr(modulation_str),
|
||||
subghz_txrx_protocol_is_transmittable(subghz->txrx, false));
|
||||
|
||||
ret = true;
|
||||
}
|
||||
furi_string_free(frequency_str);
|
||||
@@ -80,11 +84,16 @@ void subghz_scene_transmitter_on_enter(void* context) {
|
||||
}
|
||||
|
||||
bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
|
||||
// key concept: we start endless TX until user release OK button, and after this we send last
|
||||
// protocols repeats - this guarantee that one press OK will
|
||||
// be guarantee send the required minimum protocol data packets
|
||||
// for all of this we use subghz_block_generic_global.endless_tx in protocols _yield function.
|
||||
SubGhz* subghz = context;
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubGhzCustomEventViewTransmitterSendStart) {
|
||||
// user press OK - start endless TX
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
|
||||
subghz_block_generic_global.endless_tx = true;
|
||||
if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) {
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
subghz_scene_transmitter_update_data_show(subghz);
|
||||
@@ -92,29 +101,12 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterSendStop) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
int32_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
// Calling restore!
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
// Calling restore 2nd time special for FAAC SLH!
|
||||
// TODO: Find better way to restore after custom button is used!!!
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
|
||||
}
|
||||
if(subghz->fav_timeout) {
|
||||
while(scene_manager_handle_back_event(subghz->scene_manager))
|
||||
;
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
// user release OK
|
||||
// we switch off endless_tx - that mean protocols yield finish endless transmission,
|
||||
// send upload "repeat=xx" times, and after will be stoped by tick event down this code.
|
||||
subghz_block_generic_global.endless_tx = false;
|
||||
return true;
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneStart);
|
||||
return true;
|
||||
@@ -124,7 +116,33 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) {
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
// if hardware TX still working at this time so we just blink led and return
|
||||
if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
return true;
|
||||
// if hardware TX was stoped so we stop TX correctly
|
||||
} else {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK);
|
||||
int32_t tmp_counter = furi_hal_subghz_get_rolling_counter_mult();
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
// Calling restore!
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
// Calling restore 2nd time special for FAAC SLH!
|
||||
// TODO: Find better way to restore after custom button is used!!!
|
||||
subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
furi_hal_subghz_set_rolling_counter_mult(tmp_counter);
|
||||
}
|
||||
if(subghz->fav_timeout) {
|
||||
while(scene_manager_handle_back_event(subghz->scene_manager))
|
||||
;
|
||||
view_dispatcher_stop(subghz->view_dispatcher);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <applications/main/archive/helpers/archive_helpers_ext.h>
|
||||
#include <momentum/momentum.h>
|
||||
|
||||
#include "scenes/subghz_scene_start.h"
|
||||
|
||||
#include "subghz_fap.h"
|
||||
|
||||
#define TAG "SubGhzApp"
|
||||
@@ -64,7 +66,7 @@ static void subghz_rpc_command_callback(const RpcAppSystemEvent* event, void* co
|
||||
static void subghz_load_custom_presets(SubGhzSetting* setting) {
|
||||
furi_assert(setting);
|
||||
|
||||
const char* presets[][2] = {
|
||||
static const char* presets[][2] = {
|
||||
// FM95
|
||||
{"FM95",
|
||||
"02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00"},
|
||||
@@ -128,12 +130,14 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher, SubGhzViewIdMenu, submenu_get_view(subghz->submenu));
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Receiver
|
||||
subghz->subghz_receiver = subghz_view_receiver_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdReceiver,
|
||||
subghz_view_receiver_get_view(subghz->subghz_receiver));
|
||||
#endif
|
||||
}
|
||||
// Popup
|
||||
subghz->popup = popup_alloc();
|
||||
@@ -162,12 +166,14 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
//Dialog
|
||||
subghz->dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Transmitter
|
||||
subghz->subghz_transmitter = subghz_view_transmitter_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdTransmitter,
|
||||
subghz_view_transmitter_get_view(subghz->subghz_transmitter));
|
||||
#endif
|
||||
if(!alloc_for_tx_only) {
|
||||
// Variable Item List
|
||||
subghz->variable_item_list = variable_item_list_alloc();
|
||||
@@ -176,6 +182,7 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
SubGhzViewIdVariableItemList,
|
||||
variable_item_list_get_view(subghz->variable_item_list));
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Frequency Analyzer
|
||||
// View knows too much
|
||||
subghz->subghz_frequency_analyzer = subghz_frequency_analyzer_alloc(subghz->txrx);
|
||||
@@ -183,7 +190,10 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdFrequencyAnalyzer,
|
||||
subghz_frequency_analyzer_get_view(subghz->subghz_frequency_analyzer));
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Read RAW
|
||||
subghz->subghz_read_raw = subghz_read_raw_alloc(alloc_for_tx_only);
|
||||
view_dispatcher_add_view(
|
||||
@@ -229,23 +239,27 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
}
|
||||
|
||||
subghz_txrx_set_preset_internal(
|
||||
subghz->txrx, subghz->last_settings->frequency, subghz->last_settings->preset_index);
|
||||
subghz->txrx,
|
||||
subghz->last_settings->frequency,
|
||||
subghz->last_settings->preset_index,
|
||||
subghz->tx_power);
|
||||
subghz->history = subghz_history_alloc();
|
||||
}
|
||||
|
||||
subghz_rx_key_state_set(subghz, SubGhzRxKeyStateIDLE);
|
||||
|
||||
subghz->gen_info = malloc(sizeof(GenInfo));
|
||||
|
||||
if(!alloc_for_tx_only) {
|
||||
subghz->remove_duplicates = subghz->last_settings->remove_duplicates;
|
||||
subghz->ignore_filter = subghz->last_settings->ignore_filter;
|
||||
subghz->filter = subghz->last_settings->filter;
|
||||
subghz->tx_power = subghz->last_settings->tx_power;
|
||||
} else {
|
||||
subghz->filter = SubGhzProtocolFlag_Decodable;
|
||||
subghz->ignore_filter = 0x0;
|
||||
subghz->remove_duplicates = false;
|
||||
subghz->tx_power = 0;
|
||||
}
|
||||
|
||||
subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter);
|
||||
subghz_txrx_receiver_set_ignore_filter(subghz->txrx, subghz->ignore_filter);
|
||||
subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz);
|
||||
@@ -257,6 +271,9 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
subghz->last_settings->rssi = SUBGHZ_LAST_SETTING_FREQUENCY_ANALYZER_TRIGGER;
|
||||
}
|
||||
}
|
||||
#else
|
||||
subghz->gen_info = malloc(sizeof(GenInfo));
|
||||
#endif
|
||||
#if SUBGHZ_MEASURE_LOADING
|
||||
load_ticks = furi_get_tick() - load_ticks;
|
||||
FURI_LOG_I(TAG, "Loaded: %ld ms.", load_ticks);
|
||||
@@ -264,9 +281,11 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
//Init Error_str
|
||||
subghz->error_str = furi_string_alloc();
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
if(subghz->last_settings->gps_baudrate != 0) {
|
||||
subghz->gps = subghz_gps_plugin_init(subghz->last_settings->gps_baudrate);
|
||||
}
|
||||
#endif
|
||||
|
||||
return subghz;
|
||||
}
|
||||
@@ -274,6 +293,7 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_assert(subghz);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
if(subghz->rpc_ctx) {
|
||||
rpc_system_app_set_callback(subghz->rpc_ctx, NULL, NULL);
|
||||
rpc_system_app_send_exited(subghz->rpc_ctx);
|
||||
@@ -284,11 +304,14 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
subghz_txrx_speaker_off(subghz->txrx);
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz_txrx_sleep(subghz->txrx);
|
||||
#endif
|
||||
|
||||
if(!alloc_for_tx_only) {
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Receiver
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReceiver);
|
||||
subghz_view_receiver_free(subghz->subghz_receiver);
|
||||
#endif
|
||||
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTextInput);
|
||||
@@ -305,21 +328,27 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
//Dialog
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Transmitter
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTransmitter);
|
||||
subghz_view_transmitter_free(subghz->subghz_transmitter);
|
||||
#endif
|
||||
if(!alloc_for_tx_only) {
|
||||
// Variable Item List
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
variable_item_list_free(subghz->variable_item_list);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Frequency Analyzer
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdFrequencyAnalyzer);
|
||||
subghz_frequency_analyzer_free(subghz->subghz_frequency_analyzer);
|
||||
#endif
|
||||
}
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// Read RAW
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
||||
#endif
|
||||
if(!alloc_for_tx_only) {
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
@@ -339,6 +368,7 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_record_close(RECORD_GUI);
|
||||
subghz->gui = NULL;
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// threshold rssi
|
||||
subghz_threshold_rssi_free(subghz->threshold_rssi);
|
||||
|
||||
@@ -346,7 +376,9 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
subghz_history_free(subghz->history);
|
||||
}
|
||||
|
||||
#else
|
||||
free(subghz->gen_info);
|
||||
#endif
|
||||
|
||||
//TxRx
|
||||
subghz_txrx_free(subghz->txrx);
|
||||
@@ -362,18 +394,32 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_string_free(subghz->file_path);
|
||||
furi_string_free(subghz->file_path_tmp);
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
// GPS
|
||||
if(subghz->gps) {
|
||||
subghz_gps_plugin_deinit(subghz->gps);
|
||||
}
|
||||
|
||||
subghz_last_settings_free(subghz->last_settings);
|
||||
#endif
|
||||
|
||||
// The rest
|
||||
free(subghz);
|
||||
}
|
||||
|
||||
#ifndef SUBGHZ_ADD_MANUALLY
|
||||
int32_t subghz_app(char* p) {
|
||||
enum SubmenuIndex start_scene_state = 0;
|
||||
if(p) {
|
||||
if(!strcmp(p, "AddManually")) {
|
||||
start_scene_state = SubmenuIndexAddManually;
|
||||
p = NULL;
|
||||
} else if(!strcmp(p, "AddManuallyAdvanced")) {
|
||||
start_scene_state = SubmenuIndexAddManuallyAdvanced;
|
||||
p = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool alloc_for_tx;
|
||||
if(p && strlen(p)) {
|
||||
alloc_for_tx = true;
|
||||
@@ -426,6 +472,7 @@ int32_t subghz_app(char* p) {
|
||||
view_dispatcher_attach_to_gui(
|
||||
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
|
||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneStart, start_scene_state);
|
||||
if(subghz_txrx_is_database_loaded(subghz->txrx)) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneStart);
|
||||
} else {
|
||||
@@ -453,3 +500,35 @@ int32_t subghz_app(char* p) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
int32_t subghz_add_manually(void* p) {
|
||||
enum SubmenuIndex add_manually_scene_state = 0;
|
||||
if(p) {
|
||||
if(!strcmp(p, "AddManually")) {
|
||||
add_manually_scene_state = SubmenuIndexAddManually;
|
||||
} else if(!strcmp(p, "AddManuallyAdvanced")) {
|
||||
add_manually_scene_state = SubmenuIndexAddManuallyAdvanced;
|
||||
}
|
||||
}
|
||||
if(!add_manually_scene_state) return 0;
|
||||
|
||||
bool alloc_for_tx = false;
|
||||
SubGhz* subghz = subghz_alloc(alloc_for_tx);
|
||||
UNUSED(subghz_rpc_command_callback);
|
||||
UNUSED(subghz_load_custom_presets);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSetType, add_manually_scene_state);
|
||||
|
||||
view_dispatcher_attach_to_gui(
|
||||
subghz->view_dispatcher, subghz->gui, ViewDispatcherTypeFullscreen);
|
||||
furi_string_set(subghz->file_path, SUBGHZ_APP_FOLDER);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType);
|
||||
|
||||
view_dispatcher_run(subghz->view_dispatcher);
|
||||
|
||||
subghz_free(subghz, alloc_for_tx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
#include <furi.h>
|
||||
|
||||
#define SUBGHZ_HISTORY_MAX 65535 // uint16_t index max, ram limit below
|
||||
#define SUBGHZ_HISTORY_FREE_HEAP (10240 * (3 - MIN(rpc_get_sessions_count(instance->rpc), 2U)))
|
||||
// #define SUBGHZ_HISTORY_FREE_HEAP (23624 * (1 - MIN(rpc_get_sessions_count(instance->rpc), 1U)))
|
||||
#define SUBGHZ_HISTORY_FREE_HEAP (4 * 1024)
|
||||
|
||||
#define TAG "SubGhzHistory"
|
||||
|
||||
@@ -199,11 +200,11 @@ bool subghz_history_get_text_space_left(
|
||||
furi_assert(instance);
|
||||
if(!ignore_full) {
|
||||
if(memmgr_get_free_heap() < SUBGHZ_HISTORY_FREE_HEAP) {
|
||||
if(output != NULL) furi_string_printf(output, " Memory is FULL");
|
||||
if(output != NULL) furi_string_set(output, "Memory is FULL");
|
||||
return true;
|
||||
}
|
||||
if(instance->last_index_write == SUBGHZ_HISTORY_MAX) {
|
||||
if(output != NULL) furi_string_printf(output, " History is FULL");
|
||||
if(output != NULL) furi_string_set(output, "History is FULL");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -312,13 +313,6 @@ bool subghz_history_add_to_history(
|
||||
break;
|
||||
}
|
||||
furi_string_cat(instance->tmp_string, text);
|
||||
} else if(!strcmp(furi_string_get_cstr(instance->tmp_string), "Star Line")) {
|
||||
furi_string_set(instance->tmp_string, "SL ");
|
||||
if(!flipper_format_read_string(item->flipper_string, "Manufacture", text)) {
|
||||
FURI_LOG_E(TAG, "Missing Protocol");
|
||||
break;
|
||||
}
|
||||
furi_string_cat(instance->tmp_string, text);
|
||||
}
|
||||
if(!flipper_format_rewind(item->flipper_string)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
|
||||
@@ -183,14 +183,21 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
|
||||
|
||||
size_t preset_index =
|
||||
subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
|
||||
|
||||
//Edit TX power, if necessary.
|
||||
uint8_t* preset_data = subghz_setting_get_preset_data(setting, preset_index);
|
||||
size_t preset_data_size = subghz_setting_get_preset_data_size(setting, preset_index);
|
||||
subghz_txrx_set_tx_power(preset_data, preset_data_size, subghz->tx_power);
|
||||
|
||||
//Set the Updated Preset.
|
||||
subghz_txrx_set_preset(
|
||||
subghz->txrx,
|
||||
furi_string_get_cstr(temp_str),
|
||||
temp_data32,
|
||||
temp_lat,
|
||||
temp_lon,
|
||||
subghz_setting_get_preset_data(setting, preset_index),
|
||||
subghz_setting_get_preset_data_size(setting, preset_index));
|
||||
preset_data,
|
||||
preset_data_size);
|
||||
|
||||
//Load protocol
|
||||
if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
|
||||
|
||||
@@ -103,6 +103,7 @@ struct SubGhz {
|
||||
bool fav_timeout;
|
||||
FuriTimer* timer;
|
||||
|
||||
uint8_t tx_power;
|
||||
void* rpc_ctx;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_ENABLE_SOUND "Sound"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_AUTOSAVE "Autosave"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower"
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void) {
|
||||
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
|
||||
@@ -162,6 +163,12 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
1)) {
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
uint32_t tx_power = 0;
|
||||
if(!flipper_format_read_uint32(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &tx_power, 1)) {
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
instance->tx_power = (uint8_t)(tx_power & 0xFF);
|
||||
} while(0);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Error open file %s", SUBGHZ_LAST_SETTINGS_PATH);
|
||||
@@ -281,6 +288,10 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
uint32_t tx_power = instance->tx_power;
|
||||
if(!flipper_format_write_uint32(file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &tx_power, 1)) {
|
||||
break;
|
||||
}
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ typedef struct {
|
||||
bool enable_sound;
|
||||
bool autosave;
|
||||
float hopping_threshold;
|
||||
uint8_t tx_power;
|
||||
} SubGhzLastSettings;
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void);
|
||||
|
||||
@@ -435,8 +435,10 @@ void subghz_view_receiver_draw(Canvas* canvas, SubGhzViewReceiverModel* model) {
|
||||
canvas_draw_str(canvas, 44, 62, frequency_str);
|
||||
#ifdef SUBGHZ_EXT_PRESET_NAME
|
||||
if(model->history_item == 0 && model->mode == SubGhzViewReceiverModeLive) {
|
||||
canvas_draw_str(
|
||||
canvas, 44 + canvas_string_width(canvas, frequency_str) + 1, 62, "MHz");
|
||||
if(*frequency_str) {
|
||||
canvas_draw_str(
|
||||
canvas, 44 + canvas_string_width(canvas, frequency_str) + 1, 62, "MHz");
|
||||
}
|
||||
const char* str = furi_string_get_cstr(model->preset_str);
|
||||
const uint8_t vertical_offset = 7;
|
||||
const uint8_t horizontal_offset = 3;
|
||||
|
||||
@@ -151,11 +151,12 @@ void subghz_read_raw_update_sin(SubGhzReadRAW* instance) {
|
||||
}
|
||||
|
||||
static int8_t subghz_read_raw_tab_sin(uint8_t x) {
|
||||
const uint8_t tab_sin[64] = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31, 34, 37,
|
||||
40, 43, 46, 49, 51, 54, 57, 60, 63, 65, 68, 71, 73,
|
||||
76, 78, 81, 83, 85, 88, 90, 92, 94, 96, 98, 100, 102,
|
||||
104, 106, 107, 109, 111, 112, 113, 115, 116, 117, 118, 120, 121,
|
||||
122, 122, 123, 124, 125, 125, 126, 126, 126, 127, 127, 127};
|
||||
static const uint8_t tab_sin[64] = {0, 3, 6, 9, 12, 16, 19, 22, 25, 28, 31,
|
||||
34, 37, 40, 43, 46, 49, 51, 54, 57, 60, 63,
|
||||
65, 68, 71, 73, 76, 78, 81, 83, 85, 88, 90,
|
||||
92, 94, 96, 98, 100, 102, 104, 106, 107, 109, 111,
|
||||
112, 113, 115, 116, 117, 118, 120, 121, 122, 122, 123,
|
||||
124, 125, 125, 126, 126, 126, 127, 127, 127};
|
||||
|
||||
int8_t r = tab_sin[((x & 0x40) ? -x - 1 : x) & 0x3f];
|
||||
if(x & 0x80) return -r;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "canvas_i.h"
|
||||
#include "canvas.h"
|
||||
#include "icon_animation_i.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
@@ -87,6 +87,22 @@ extern const CanvasFontParameters canvas_font_params[FontTotalNumber];
|
||||
/** Canvas anonymous structure */
|
||||
typedef struct Canvas Canvas;
|
||||
|
||||
/** Get canvas buffer.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return pointer to buffer
|
||||
*/
|
||||
uint8_t* canvas_get_buffer(Canvas* canvas);
|
||||
|
||||
/** Get canvas buffer size.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return size of canvas in bytes
|
||||
*/
|
||||
size_t canvas_get_buffer_size(const Canvas* canvas);
|
||||
|
||||
/** Reset canvas drawing tools configuration
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
|
||||
@@ -61,22 +61,6 @@ Canvas* canvas_init(void);
|
||||
*/
|
||||
void canvas_free(Canvas* canvas);
|
||||
|
||||
/** Get canvas buffer.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return pointer to buffer
|
||||
*/
|
||||
uint8_t* canvas_get_buffer(Canvas* canvas);
|
||||
|
||||
/** Get canvas buffer size.
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
*
|
||||
* @return size of canvas in bytes
|
||||
*/
|
||||
size_t canvas_get_buffer_size(const Canvas* canvas);
|
||||
|
||||
/** Set drawing region relative to real screen buffer
|
||||
*
|
||||
* @param canvas Canvas instance
|
||||
|
||||
@@ -20,44 +20,15 @@ static const NotificationSequence sequence_note_c = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
#define CONTRAST_COUNT 17
|
||||
#define CONTRAST_COUNT 29
|
||||
const char* const contrast_text[CONTRAST_COUNT] = {
|
||||
"-8",
|
||||
"-7",
|
||||
"-6",
|
||||
"-5",
|
||||
"-4",
|
||||
"-3",
|
||||
"-2",
|
||||
"-1",
|
||||
"0",
|
||||
"+1",
|
||||
"+2",
|
||||
"+3",
|
||||
"+4",
|
||||
"+5",
|
||||
"+6",
|
||||
"+7",
|
||||
"+8",
|
||||
"-10", "-9", "-8", "-7", "-6", "-5", "-4", "-3", "-2", "-1",
|
||||
"0", "+1", "+2", "+3", "+4", "+5", "+6", "+7", "+8", "+9",
|
||||
"+10", "+11", "+12", "+13", "+14", "+15", "+16", "+17", "+18",
|
||||
};
|
||||
const int32_t contrast_value[CONTRAST_COUNT] = {
|
||||
-8,
|
||||
-7,
|
||||
-6,
|
||||
-5,
|
||||
-4,
|
||||
-3,
|
||||
-2,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4,
|
||||
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
|
||||
};
|
||||
|
||||
#define BACKLIGHT_COUNT 21
|
||||
|
||||
@@ -156,14 +156,26 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
if(sd_status == FSE_OK) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
} else {
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,13 @@ bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManag
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsBenchmark);
|
||||
|
||||
@@ -44,7 +44,13 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
scene_manager_set_scene_state(app->scene_manager, StorageSettingsFactoryReset, 0);
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
counter++;
|
||||
|
||||
@@ -41,7 +41,13 @@ bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerE
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_set_scene_state(app->scene_manager, StorageSettingsFormatting, false);
|
||||
|
||||
@@ -75,8 +75,14 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
|
||||
@@ -56,7 +56,13 @@ bool storage_settings_scene_internal_info_on_event(void* context, SceneManagerEv
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,13 @@ bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent ev
|
||||
switch(event.event) {
|
||||
case DialogExResultLeft:
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted);
|
||||
|
||||
@@ -46,7 +46,13 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
case DialogExResultLeft:
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_previous_scene(app->scene_manager);
|
||||
}
|
||||
break;
|
||||
case DialogExResultRight:
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsUnmounted);
|
||||
|
||||
@@ -57,8 +57,14 @@ bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case DialogExResultCenter:
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
if(app->from_favorites) {
|
||||
scene_manager_stop(app->scene_manager);
|
||||
view_dispatcher_stop(app->view_dispatcher);
|
||||
return true;
|
||||
} else {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
app->scene_manager, StorageSettingsStart);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
|
||||
@@ -99,7 +99,10 @@ int32_t storage_settings_app(void* p) {
|
||||
StorageSettings* app = storage_settings_alloc();
|
||||
|
||||
if(!submenu_settings_helpers_app_start(app->settings_helper, p)) {
|
||||
app->from_favorites = false;
|
||||
scene_manager_next_scene(app->scene_manager, StorageSettingsStart);
|
||||
} else {
|
||||
app->from_favorites = true;
|
||||
}
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
@@ -44,6 +44,8 @@ typedef struct {
|
||||
// helpers
|
||||
SubmenuSettingsHelperDescriptor* helper_descriptor;
|
||||
SubmenuSettingsHelper* settings_helper;
|
||||
|
||||
bool from_favorites;
|
||||
} StorageSettings;
|
||||
|
||||
typedef enum {
|
||||
|
||||
|
After Width: | Height: | Size: 80 B |
@@ -1,19 +1,60 @@
|
||||
#include "hid_ptt.h"
|
||||
#include "hid_ptt_menu.h"
|
||||
#include <gui/elements.h>
|
||||
#include <locale/locale.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <furi_hal_power.h>
|
||||
#include <furi_hal_rtc.h>
|
||||
#include "../hid.h"
|
||||
#include "../views.h"
|
||||
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidPushToTalk"
|
||||
#define TAG "HidPushToTalk"
|
||||
#define HID_PTT_LEFT_HOLD_ANIM_STEP_MS 100U
|
||||
#define HID_PTT_LEFT_HOLD_ANIM_STEPS 5U
|
||||
|
||||
// Exact home status-bar Bluetooth icon pixels (from assets/icons/StatusBar).
|
||||
// Bitmap format for canvas_draw_bitmap(): row-major, 1-bit.
|
||||
// Full compressed frame data including leading 0x00 heatshrink marker,
|
||||
// copied verbatim from build/f7-firmware-D/assets/compiled/assets_icons.c
|
||||
static const uint8_t hid_ptt_bluetooth_connected_16x8_bits[] = {
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x16,
|
||||
0x60,
|
||||
0x4c,
|
||||
0x97,
|
||||
0x4c,
|
||||
0x97,
|
||||
0x16,
|
||||
0x60,
|
||||
0x0d,
|
||||
0x00,
|
||||
0x04,
|
||||
0x00,
|
||||
};
|
||||
static const uint8_t hid_ptt_bluetooth_idle_5x8_bits[] = {
|
||||
0x00,
|
||||
0x04,
|
||||
0x0d,
|
||||
0x16,
|
||||
0x0c,
|
||||
0x0c,
|
||||
0x16,
|
||||
0x0d,
|
||||
0x04,
|
||||
};
|
||||
|
||||
struct HidPushToTalk {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
Widget* help;
|
||||
FuriTimer* left_hold_timer;
|
||||
};
|
||||
|
||||
typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt);
|
||||
@@ -32,6 +73,7 @@ typedef struct {
|
||||
size_t osIndex;
|
||||
size_t appIndex;
|
||||
size_t window_position;
|
||||
uint8_t left_hold_progress;
|
||||
PushToTalkActionCallback callback_trigger_mute;
|
||||
PushToTalkActionCallback callback_trigger_camera;
|
||||
PushToTalkActionCallback callback_trigger_hand;
|
||||
@@ -59,6 +101,66 @@ enum HidPushToTalkAppIndex {
|
||||
HidPushToTalkAppIndexSize,
|
||||
};
|
||||
|
||||
static bool hid_ptt_is_zoom_app(size_t app_index) {
|
||||
return (app_index == HidPushToTalkAppIndexZoom) ||
|
||||
(app_index == HidPushToTalkAppIndexZoomGlobal);
|
||||
}
|
||||
|
||||
static void hid_ptt_left_hold_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalk* hid_ptt = context;
|
||||
with_view_model(
|
||||
hid_ptt->view,
|
||||
HidPushToTalkModel * model,
|
||||
{
|
||||
if(model->left_pressed && hid_ptt_is_zoom_app(model->appIndex)) {
|
||||
if(model->left_hold_progress < HID_PTT_LEFT_HOLD_ANIM_STEPS) {
|
||||
model->left_hold_progress++;
|
||||
}
|
||||
} else {
|
||||
model->left_hold_progress = 0;
|
||||
furi_timer_stop(hid_ptt->left_hold_timer);
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_ptt_draw_zoom_enter_hint(
|
||||
Canvas* canvas,
|
||||
uint8_t x,
|
||||
uint8_t y,
|
||||
uint8_t progress,
|
||||
bool pressed) {
|
||||
const uint8_t width = 18;
|
||||
const uint8_t height = 10;
|
||||
const uint8_t inner_x = x + 1;
|
||||
const uint8_t inner_y = y + 1;
|
||||
const uint8_t inner_w = width - 2;
|
||||
const uint8_t inner_h = height - 1;
|
||||
|
||||
if(progress > 0) {
|
||||
const uint8_t fill_w = (inner_w * progress) / HID_PTT_LEFT_HOLD_ANIM_STEPS;
|
||||
if(fill_w > 0) {
|
||||
canvas_draw_box(canvas, inner_x, inner_y, fill_w, inner_h - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(uint8_t dot_x = x; dot_x < x + width; dot_x += 2) {
|
||||
canvas_draw_dot(canvas, dot_x, y + height - 1);
|
||||
}
|
||||
for(uint8_t dot_y = y + 1; dot_y < y + height; dot_y += 2) {
|
||||
canvas_draw_dot(canvas, x, dot_y);
|
||||
canvas_draw_dot(canvas, x + width - 1, dot_y);
|
||||
}
|
||||
|
||||
UNUSED(pressed);
|
||||
if(progress > 0) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
}
|
||||
canvas_draw_icon(canvas, x + 4, y + 1, &I_Enter_11x7);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
}
|
||||
|
||||
// meet, zoom
|
||||
static void hid_ptt_start_ptt_meet_zoom(HidPushToTalk* hid_ptt) {
|
||||
hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_SPACEBAR);
|
||||
@@ -128,9 +230,9 @@ static void hid_ptt_trigger_hand_zoom(HidPushToTalk* hid_ptt) {
|
||||
// zoom global macos
|
||||
static void hid_ptt_trigger_mute_macos_zoom_global(HidPushToTalk* hid_ptt) {
|
||||
hid_hal_keyboard_press(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_N);
|
||||
hid_hal_keyboard_release(
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_M);
|
||||
hid_ptt->hid, KEY_MOD_LEFT_GUI | KEY_MOD_RIGHT_ALT | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_N);
|
||||
}
|
||||
|
||||
static void hid_ptt_trigger_camera_macos_zoom_global(HidPushToTalk* hid_ptt) {
|
||||
@@ -387,6 +489,97 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) {
|
||||
hid_ptt->hid, KEY_MOD_LEFT_CTRL | KEY_MOD_LEFT_SHIFT | HID_KEYBOARD_A);
|
||||
}
|
||||
|
||||
static void hid_ptt_populate_help(HidPushToTalk* hid_ptt, uint32_t appIndex) {
|
||||
widget_reset(hid_ptt->help);
|
||||
char* app_specific_help = "";
|
||||
switch(appIndex) {
|
||||
case HidPushToTalkAppIndexGoogleMeet:
|
||||
app_specific_help =
|
||||
"Google Meet:\n"
|
||||
"This feature is off by default in your audio settings "
|
||||
"and may not work for Windows users who use their screen "
|
||||
"reader. In this situation, the spacebar performs a different action.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexGoogleMeetGlobal:
|
||||
app_specific_help = "Google Meet (Global):\n"
|
||||
"1. Install \"Google Meet - Global Shortcuts\" extension.\n"
|
||||
"2. Open chrome://extensions/shortcuts.\n"
|
||||
"3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n"
|
||||
"4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n"
|
||||
"5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexDiscord:
|
||||
app_specific_help = "Discord:\n"
|
||||
"1. Under App Settings, click Voice & Video. Under Input Mode, "
|
||||
"check the box next to Push to Talk.\n"
|
||||
"2. Scroll down to SHORTCUT, click Record Keybinder.\n"
|
||||
"3. Press PTT in the app to bind it."
|
||||
"4. Go to Keybinds and assign mute button.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexTeamSpeak:
|
||||
app_specific_help = "TeamSpeak:\n"
|
||||
"To make keys working bind them in TeamSpeak settings.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexTeams:
|
||||
app_specific_help =
|
||||
"Teams:\n"
|
||||
"Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexZoomGlobal:
|
||||
app_specific_help = "Zoom (Global):\n"
|
||||
"1. Go to Settings > Keyboard Shortcuts.\n"
|
||||
"2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n"
|
||||
"3. Press the Mute button in the app to bind it.\n"
|
||||
"4. Check global checkbox.\n"
|
||||
"5. Repeat for video and hand shortcuts.\n"
|
||||
"6. Long-press < to send Enter key.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexZoom:
|
||||
app_specific_help = "Zoom:\n"
|
||||
"1. Go to Settings > Keyboard Shortcuts.\n"
|
||||
"2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n"
|
||||
"3. Press the Mute button in the app to bind it.\n"
|
||||
"4. Repeat for video and hand shortcuts.\n"
|
||||
"5. Long-press < to send Enter key.\n\n";
|
||||
break;
|
||||
}
|
||||
char* left_button_help = "";
|
||||
if(appIndex == HidPushToTalkAppIndexZoom || appIndex == HidPushToTalkAppIndexZoomGlobal) {
|
||||
left_button_help = "Long-press < sends Enter.\n";
|
||||
}
|
||||
FuriString* msg = furi_string_alloc();
|
||||
furi_string_cat_printf(
|
||||
msg,
|
||||
"%sGeneral:\n"
|
||||
"To operate properly flipper microphone "
|
||||
"status must be in sync with your computer.\n"
|
||||
"Hold > to change mic status.\n"
|
||||
"%s"
|
||||
"Long-press OK in menu to open this help.\n"
|
||||
"Press BACK to switch mic on/off.\n"
|
||||
"Hold 'o' for PTT mode (mic will be off once you release 'o')\n"
|
||||
"Hold BACK to exit.",
|
||||
app_specific_help,
|
||||
left_button_help);
|
||||
widget_add_text_scroll_element(hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg));
|
||||
furi_string_free(msg);
|
||||
}
|
||||
|
||||
static void hid_ptt_menu_help_callback(
|
||||
void* context,
|
||||
uint32_t osIndex,
|
||||
FuriString* osLabel,
|
||||
uint32_t appIndex,
|
||||
FuriString* appLabel) {
|
||||
UNUSED(osIndex);
|
||||
UNUSED(osLabel);
|
||||
UNUSED(appLabel);
|
||||
furi_assert(context);
|
||||
HidPushToTalk* hid_ptt = context;
|
||||
hid_ptt_populate_help(hid_ptt, appIndex);
|
||||
view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
|
||||
}
|
||||
|
||||
static void hid_ptt_menu_callback(
|
||||
void* context,
|
||||
uint32_t osIndex,
|
||||
@@ -593,68 +786,9 @@ static void hid_ptt_menu_callback(
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
char* app_specific_help = "";
|
||||
switch(appIndex) {
|
||||
case HidPushToTalkAppIndexGoogleMeet:
|
||||
app_specific_help =
|
||||
"Google Meet:\n"
|
||||
"This feature is off by default in your audio settings "
|
||||
"and may not work for Windows users who use their screen "
|
||||
"reader. In this situation, the spacebar performs a different action.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexGoogleMeetGlobal:
|
||||
app_specific_help = "Google Meet (Global):\n"
|
||||
"1. Install \"Google Meet - Global Shortcuts\" extension.\n"
|
||||
"2. Open chrome://extensions/shortcuts.\n"
|
||||
"3. Set 'Toggle microphone' to Cmd+Ctrl+7 and enable Global.\n"
|
||||
"4. Set 'Toggle camera' to Cmd+Ctrl+8 and enable Global.\n"
|
||||
"5. Set 'Raise hand' to Cmd+Ctrl+9 and enable Global.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexDiscord:
|
||||
app_specific_help =
|
||||
"Discord:\n"
|
||||
"1. Under App Settings, click Voice & Video. Under Input Mode, "
|
||||
"check the box next to Push to Talk.\n"
|
||||
"2. Scroll down to SHORTCUT, click Record Keybinder.\n"
|
||||
"3. Press PTT in the app to bind it."
|
||||
"4. Go to Keybinds and assign mute button.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexTeamSpeak:
|
||||
app_specific_help = "TeamSpeak:\n"
|
||||
"To make keys working bind them in TeamSpeak settings.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexTeams:
|
||||
app_specific_help =
|
||||
"Teams:\n"
|
||||
"Go to Settings > Privacy. Make sure Keyboard shortcut to unmute is toggled on.\n\n";
|
||||
break;
|
||||
case HidPushToTalkAppIndexZoomGlobal:
|
||||
app_specific_help = "Zoom (Global):\n"
|
||||
"1. Go to Settings > Keyboard Shortcuts.\n"
|
||||
"2. Find the 'Mute/Unmute' shortcut and click 'Edit'.\n"
|
||||
"3. Press the Mute button in the app to bind it.\n"
|
||||
"4. Check global checkbox.\n"
|
||||
"5. Repeat for video and hand shortcuts.\n\n";
|
||||
}
|
||||
|
||||
FuriString* msg = furi_string_alloc();
|
||||
furi_string_cat_printf(
|
||||
msg,
|
||||
"%sGeneral:\n"
|
||||
"To operate properly flipper microphone "
|
||||
"status must be in sync with your computer.\n"
|
||||
"Hold > to change mic status.\n"
|
||||
"Hold < to open this help.\n"
|
||||
"Press BACK to switch mic on/off.\n"
|
||||
"Hold 'o' for PTT mode (mic will be off once you release 'o')\n"
|
||||
"Hold BACK to exit.",
|
||||
app_specific_help);
|
||||
widget_add_text_scroll_element(
|
||||
hid_ptt->help, 0, 0, 128, 64, furi_string_get_cstr(msg));
|
||||
furi_string_free(msg);
|
||||
},
|
||||
true);
|
||||
hid_ptt_populate_help(hid_ptt, appIndex);
|
||||
view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk);
|
||||
}
|
||||
|
||||
@@ -673,43 +807,148 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st
|
||||
furi_string_free(disp_str);
|
||||
}
|
||||
|
||||
static void hid_ptt_draw_app_label(Canvas* canvas, uint8_t first_line_y, FuriString* app) {
|
||||
FuriString* first_line = furi_string_alloc_set(app);
|
||||
if(canvas_string_width(canvas, furi_string_get_cstr(first_line)) <= canvas_width(canvas)) {
|
||||
hid_ptt_draw_text_centered(canvas, first_line_y, first_line);
|
||||
furi_string_free(first_line);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* app_cstr = furi_string_get_cstr(app);
|
||||
const char* split = strrchr(app_cstr, ' ');
|
||||
if(!split) {
|
||||
hid_ptt_draw_text_centered(canvas, first_line_y, first_line);
|
||||
furi_string_free(first_line);
|
||||
return;
|
||||
}
|
||||
|
||||
FuriString* second_line = furi_string_alloc();
|
||||
furi_string_set_strn(first_line, app_cstr, split - app_cstr);
|
||||
furi_string_set_str(second_line, split + 1);
|
||||
|
||||
elements_string_fit_width(canvas, first_line, canvas_width(canvas));
|
||||
elements_string_fit_width(canvas, second_line, canvas_width(canvas));
|
||||
hid_ptt_draw_text_centered(canvas, first_line_y, first_line);
|
||||
hid_ptt_draw_text_centered(canvas, first_line_y + 10, second_line);
|
||||
|
||||
furi_string_free(second_line);
|
||||
furi_string_free(first_line);
|
||||
}
|
||||
|
||||
static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) {
|
||||
char time_str[16];
|
||||
DateTime dt;
|
||||
furi_hal_rtc_get_datetime(&dt);
|
||||
|
||||
uint8_t hour = dt.hour;
|
||||
if(locale_get_time_format() == LocaleTimeFormat12h) {
|
||||
if(hour > 12) {
|
||||
hour -= 12;
|
||||
}
|
||||
if(hour == 0) {
|
||||
hour = 12;
|
||||
}
|
||||
}
|
||||
snprintf(time_str, sizeof(time_str), "%02u:%02u", hour, dt.minute);
|
||||
|
||||
uint8_t battery = furi_hal_power_get_pct();
|
||||
if(battery > 100) {
|
||||
battery = 100;
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 0, 0, 64, 13);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, 0, 0, 64, 13, 1);
|
||||
canvas_draw_line(canvas, 1, 11, 62, 11);
|
||||
|
||||
if(show_bt) {
|
||||
if(connected) {
|
||||
canvas_draw_bitmap(canvas, 2, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits);
|
||||
} else {
|
||||
canvas_draw_bitmap(canvas, 2, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits);
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t battery_x = 48;
|
||||
const uint8_t battery_y = 3;
|
||||
const uint8_t battery_w = 13;
|
||||
const uint8_t battery_h = 6;
|
||||
canvas_draw_frame(canvas, battery_x, battery_y, battery_w, battery_h);
|
||||
canvas_draw_box(canvas, battery_x + battery_w, battery_y + 2, 1, 2);
|
||||
canvas_draw_box(canvas, battery_x + 1, battery_y + 1, ((battery_w - 2) * battery) / 100, 4);
|
||||
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 33, 10, AlignCenter, AlignBottom, time_str);
|
||||
}
|
||||
|
||||
static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalkModel* model = context;
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
#ifdef HID_TRANSPORT_BLE
|
||||
if(model->connected) {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_connected_15x15);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, 0, 0, &I_Ble_disconnected_15x15);
|
||||
const bool app_label_needs_two_lines =
|
||||
canvas_string_width(canvas, furi_string_get_cstr(model->app)) > canvas_width(canvas);
|
||||
|
||||
const uint8_t top_offset = 13;
|
||||
const uint8_t status_bar_bottom_y = top_offset;
|
||||
|
||||
// For Zoom/Zoom Global, keep helper banner higher to show Enter key hint space
|
||||
// For other apps, move it down to close the gap
|
||||
uint8_t helper_top_y = 102;
|
||||
if((model->appIndex == HidPushToTalkAppIndexZoom ||
|
||||
model->appIndex == HidPushToTalkAppIndexZoomGlobal) &&
|
||||
!app_label_needs_two_lines) {
|
||||
helper_top_y = 92;
|
||||
}
|
||||
#endif
|
||||
|
||||
// OS and App labels
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
hid_ptt_draw_text_centered(canvas, 73, model->app);
|
||||
hid_ptt_draw_text_centered(canvas, 84, model->os);
|
||||
|
||||
// Help label
|
||||
canvas_draw_icon(canvas, 0, 88, &I_Help_top_64x17);
|
||||
canvas_draw_line(canvas, 4, 105, 4, 114);
|
||||
canvas_draw_line(canvas, 63, 105, 63, 114);
|
||||
canvas_draw_icon(canvas, 7, 107, &I_Hold_15x5);
|
||||
canvas_draw_icon(canvas, 24, 105, &I_BtnLeft_9x9);
|
||||
canvas_draw_icon(canvas, 34, 108, &I_for_help_27x5);
|
||||
canvas_draw_icon(canvas, 0, 115, &I_Help_exit_64x9);
|
||||
canvas_draw_icon(canvas, 24, 115, &I_BtnBackV_9x9);
|
||||
|
||||
const uint8_t x_1 = 0;
|
||||
const uint8_t x_2 = x_1 + 19 + 4;
|
||||
const uint8_t x_3 = x_1 + 19 * 2 + 8;
|
||||
|
||||
const uint8_t y_1 = 3;
|
||||
const uint8_t y_1 = top_offset + 3;
|
||||
const uint8_t y_2 = y_1 + 19;
|
||||
const uint8_t y_3 = y_2 + 19;
|
||||
|
||||
const uint8_t controls_bottom_y = y_3 + 18;
|
||||
const uint8_t labels_center_y = (controls_bottom_y + helper_top_y) / 2;
|
||||
const uint8_t app_label_y = app_label_needs_two_lines ? (controls_bottom_y + 10) :
|
||||
(labels_center_y - 1);
|
||||
const uint8_t os_label_y = app_label_needs_two_lines ? (app_label_y + 20) : (app_label_y + 11);
|
||||
|
||||
// Header
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
#ifdef HID_TRANSPORT_BLE
|
||||
hid_ptt_draw_status_bar(canvas, true, model->connected);
|
||||
#else
|
||||
hid_ptt_draw_status_bar(canvas, false, false);
|
||||
#endif
|
||||
|
||||
// OS and App labels
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(app_label_needs_two_lines) {
|
||||
hid_ptt_draw_app_label(canvas, app_label_y, model->app);
|
||||
} else {
|
||||
hid_ptt_draw_text_centered(canvas, app_label_y, model->app);
|
||||
}
|
||||
hid_ptt_draw_text_centered(canvas, os_label_y, model->os);
|
||||
|
||||
// Help label
|
||||
canvas_draw_icon(canvas, 0, helper_top_y, &I_Help_top_64x17);
|
||||
canvas_draw_line(canvas, 4, 109, 4, 118);
|
||||
canvas_draw_line(canvas, 63, 109, 63, 118);
|
||||
canvas_draw_icon(canvas, 0, 119, &I_Help_exit_64x9);
|
||||
canvas_draw_icon(canvas, 24, 119, &I_BtnBackV_9x9);
|
||||
|
||||
// For Zoom/Zoom Global: show "Hold < for Enter" hint
|
||||
if(model->appIndex == HidPushToTalkAppIndexZoom ||
|
||||
model->appIndex == HidPushToTalkAppIndexZoomGlobal) {
|
||||
canvas_draw_icon(canvas, 7, 111, &I_Hold_15x5);
|
||||
canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9);
|
||||
canvas_draw_icon(canvas, 35, 112, &I_for_11x5);
|
||||
canvas_draw_icon(canvas, 48, 112, &I_Return_10x7);
|
||||
}
|
||||
|
||||
// Up
|
||||
canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18);
|
||||
if(model->up_pressed) {
|
||||
@@ -741,6 +980,11 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
|
||||
}
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
|
||||
if(hid_ptt_is_zoom_app(model->appIndex)) {
|
||||
hid_ptt_draw_zoom_enter_hint(
|
||||
canvas, x_1, y_2 + 18, model->left_hold_progress, model->left_pressed);
|
||||
}
|
||||
|
||||
// Right / Camera
|
||||
canvas_draw_icon(canvas, x_3, y_2, &I_Button_18x18);
|
||||
if(model->right_pressed) {
|
||||
@@ -756,23 +1000,24 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) {
|
||||
|
||||
// Back / Mic
|
||||
const uint8_t x_mic = x_3;
|
||||
canvas_draw_icon(canvas, x_mic, 0, &I_RoundButtonUnpressed_16x16);
|
||||
const uint8_t y_mic = status_bar_bottom_y + ((y_2 - status_bar_bottom_y - 16) / 2);
|
||||
canvas_draw_icon(canvas, x_mic, y_mic, &I_RoundButtonUnpressed_16x16);
|
||||
|
||||
if(!(!model->muted || (model->ptt_pressed))) {
|
||||
// show muted
|
||||
if(model->mic_pressed) {
|
||||
// canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressedCrossed_15x15);
|
||||
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedCrossedBtn_16x16);
|
||||
canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophonePressedCrossedBtn_16x16);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophoneCrossed_16x16);
|
||||
canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophoneCrossed_16x16);
|
||||
}
|
||||
} else {
|
||||
// show unmuted
|
||||
if(model->mic_pressed) {
|
||||
// canvas_draw_icon(canvas, x_mic + 1, 0, &I_MicrophonePressed_15x15);
|
||||
canvas_draw_icon(canvas, x_mic, 0, &I_MicrophonePressedBtn_16x16);
|
||||
canvas_draw_icon(canvas, x_mic, y_mic, &I_MicrophonePressedBtn_16x16);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x_mic + 5, 2, &I_Mic_7x11);
|
||||
canvas_draw_icon(canvas, x_mic + 5, y_mic + 2, &I_Mic_7x11);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -813,6 +1058,12 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
|
||||
hid_hal_consumer_key_press(hid_ptt->hid, HID_CONSUMER_VOLUME_DECREMENT);
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = true;
|
||||
if(hid_ptt_is_zoom_app(model->appIndex)) {
|
||||
model->left_hold_progress = 0;
|
||||
furi_timer_start(
|
||||
hid_ptt->left_hold_timer,
|
||||
furi_ms_to_ticks(HID_PTT_LEFT_HOLD_ANIM_STEP_MS));
|
||||
}
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = true;
|
||||
} else if(event->key == InputKeyOk) {
|
||||
@@ -836,6 +1087,8 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
|
||||
}
|
||||
} else if(event->key == InputKeyLeft) {
|
||||
model->left_pressed = false;
|
||||
model->left_hold_progress = 0;
|
||||
furi_timer_stop(hid_ptt->left_hold_timer);
|
||||
} else if(event->key == InputKeyRight) {
|
||||
model->right_pressed = false;
|
||||
|
||||
@@ -866,10 +1119,14 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) {
|
||||
model->muted = !model->muted;
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
} else if(event->type == InputTypeLong && event->key == InputKeyLeft) {
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
model->left_pressed = false;
|
||||
view_dispatcher_switch_to_view(
|
||||
hid_ptt->hid->view_dispatcher, HidViewPushToTalkHelp);
|
||||
if(model->appIndex == HidPushToTalkAppIndexZoom ||
|
||||
model->appIndex == HidPushToTalkAppIndexZoomGlobal) {
|
||||
model->left_hold_progress = HID_PTT_LEFT_HOLD_ANIM_STEPS;
|
||||
furi_timer_stop(hid_ptt->left_hold_timer);
|
||||
hid_hal_keyboard_press(hid_ptt->hid, HID_KEYBOARD_RETURN);
|
||||
hid_hal_keyboard_release(hid_ptt->hid, HID_KEYBOARD_RETURN);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_single_vibro);
|
||||
}
|
||||
}
|
||||
//LED
|
||||
if(!model->muted || (model->ptt_pressed)) {
|
||||
@@ -886,6 +1143,7 @@ static bool hid_ptt_input_callback(InputEvent* event, void* context) {
|
||||
HidPushToTalk* hid_ptt = context;
|
||||
bool consumed = false;
|
||||
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||
furi_timer_stop(hid_ptt->left_hold_timer);
|
||||
hid_hal_keyboard_release_all(hid_ptt->hid);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_double_vibro);
|
||||
widget_reset(hid_ptt->help);
|
||||
@@ -901,14 +1159,16 @@ View* hid_ptt_get_view(HidPushToTalk* hid_ptt) {
|
||||
return hid_ptt->view;
|
||||
}
|
||||
|
||||
static uint32_t hid_ptt_view(void* context) {
|
||||
static uint32_t hid_ptt_menu_view(void* context) {
|
||||
UNUSED(context);
|
||||
return HidViewPushToTalk;
|
||||
return HidViewPushToTalkMenu;
|
||||
}
|
||||
|
||||
HidPushToTalk* hid_ptt_alloc(Hid* hid) {
|
||||
HidPushToTalk* hid_ptt = malloc(sizeof(HidPushToTalk));
|
||||
hid_ptt->hid = hid;
|
||||
hid_ptt->left_hold_timer =
|
||||
furi_timer_alloc(hid_ptt_left_hold_timer_callback, FuriTimerTypePeriodic, hid_ptt);
|
||||
hid_ptt->view = view_alloc();
|
||||
view_set_context(hid_ptt->view, hid_ptt);
|
||||
view_allocate_model(hid_ptt->view, ViewModelTypeLocking, sizeof(HidPushToTalkModel));
|
||||
@@ -921,6 +1181,7 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) {
|
||||
HidPushToTalkModel * model,
|
||||
{
|
||||
model->muted = true; // assume we're muted
|
||||
model->left_hold_progress = 0;
|
||||
model->os = furi_string_alloc();
|
||||
model->app = furi_string_alloc();
|
||||
},
|
||||
@@ -1134,14 +1395,17 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) {
|
||||
hid_ptt);
|
||||
|
||||
hid_ptt->help = widget_alloc();
|
||||
view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_view);
|
||||
view_set_previous_callback(widget_get_view(hid_ptt->help), hid_ptt_menu_view);
|
||||
view_dispatcher_add_view(
|
||||
hid->view_dispatcher, HidViewPushToTalkHelp, widget_get_view(hid_ptt->help));
|
||||
ptt_menu_set_long_ok_callback(hid->hid_ptt_menu, hid_ptt_menu_help_callback, hid_ptt);
|
||||
return hid_ptt;
|
||||
}
|
||||
|
||||
void hid_ptt_free(HidPushToTalk* hid_ptt) {
|
||||
furi_assert(hid_ptt);
|
||||
furi_timer_stop(hid_ptt->left_hold_timer);
|
||||
furi_timer_free(hid_ptt->left_hold_timer);
|
||||
notification_message(hid_ptt->hid->notifications, &sequence_reset_red);
|
||||
with_view_model(
|
||||
hid_ptt->view,
|
||||
|
||||
@@ -5,11 +5,18 @@
|
||||
#include "../hid.h"
|
||||
#include "../views.h"
|
||||
|
||||
#define TAG "HidPushToTalkMenu"
|
||||
#include "hid_icons.h"
|
||||
|
||||
#define TAG "HidPushToTalkMenu"
|
||||
#define PTT_MENU_HELP_HINT_DELAY_MS 5000U
|
||||
#define PTT_MENU_HINT_TIMER_PERIOD_MS 150U
|
||||
|
||||
struct HidPushToTalkMenu {
|
||||
View* view;
|
||||
Hid* hid;
|
||||
PushToTalkMenuLongOkCallback long_ok_callback;
|
||||
void* long_ok_callback_context;
|
||||
FuriTimer* hint_timer;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
@@ -60,8 +67,69 @@ typedef struct {
|
||||
size_t window_position;
|
||||
PushToTalkMenuList* lists;
|
||||
int lists_count;
|
||||
uint32_t last_interaction_tick;
|
||||
size_t hint_list_position;
|
||||
size_t hint_item_position;
|
||||
bool hint_visible;
|
||||
uint16_t hint_scroll_tick;
|
||||
} HidPushToTalkMenuModel;
|
||||
|
||||
static void hid_ptt_menu_mark_interaction(HidPushToTalkMenu* hid_ptt_menu) {
|
||||
with_view_model(
|
||||
hid_ptt_menu->view,
|
||||
HidPushToTalkMenuModel * model,
|
||||
{
|
||||
model->last_interaction_tick = furi_get_tick();
|
||||
model->hint_list_position = model->list_position;
|
||||
model->hint_item_position = model->position;
|
||||
model->hint_visible = false;
|
||||
model->hint_scroll_tick = 0;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_ptt_menu_hint_timer_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalkMenu* hid_ptt_menu = context;
|
||||
with_view_model(
|
||||
hid_ptt_menu->view,
|
||||
HidPushToTalkMenuModel * model,
|
||||
{
|
||||
const uint32_t now = furi_get_tick();
|
||||
const bool selection_changed = (model->list_position != model->hint_list_position) ||
|
||||
(model->position != model->hint_item_position);
|
||||
|
||||
if(selection_changed) {
|
||||
model->hint_list_position = model->list_position;
|
||||
model->hint_item_position = model->position;
|
||||
model->last_interaction_tick = now;
|
||||
model->hint_visible = false;
|
||||
model->hint_scroll_tick = 0;
|
||||
} else if(!model->hint_visible) {
|
||||
if((now - model->last_interaction_tick) >= PTT_MENU_HELP_HINT_DELAY_MS) {
|
||||
model->hint_visible = true;
|
||||
model->hint_scroll_tick = 0;
|
||||
}
|
||||
} else {
|
||||
model->hint_scroll_tick++;
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
static void hid_ptt_menu_enter_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalkMenu* hid_ptt_menu = context;
|
||||
hid_ptt_menu_mark_interaction(hid_ptt_menu);
|
||||
furi_timer_start(hid_ptt_menu->hint_timer, furi_ms_to_ticks(PTT_MENU_HINT_TIMER_PERIOD_MS));
|
||||
}
|
||||
|
||||
static void hid_ptt_menu_exit_callback(void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalkMenu* hid_ptt_menu = context;
|
||||
furi_timer_stop(hid_ptt_menu->hint_timer);
|
||||
}
|
||||
|
||||
static void
|
||||
hid_ptt_menu_draw_list(Canvas* canvas, void* context, const PushToTalkMenuItemArray_t items) {
|
||||
furi_assert(context);
|
||||
@@ -94,13 +162,44 @@ static void
|
||||
|
||||
FuriString* disp_str;
|
||||
disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label);
|
||||
elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
|
||||
const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4;
|
||||
const int32_t row_left_x = 6;
|
||||
const int32_t row_right_x = item_width - 2;
|
||||
const size_t row_width = row_right_x - row_left_x;
|
||||
|
||||
canvas_draw_str(
|
||||
canvas,
|
||||
6,
|
||||
y_offset + (item_position * item_height) + item_height - 4,
|
||||
furi_string_get_cstr(disp_str));
|
||||
if((position == model->position) && model->hint_visible) {
|
||||
const char* hint_prefix = " | Long press";
|
||||
const char* hint_suffix = "for help";
|
||||
const int32_t icon_y = text_y - 8;
|
||||
|
||||
const size_t label_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str));
|
||||
const size_t prefix_w = canvas_string_width(canvas, hint_prefix);
|
||||
const size_t icon_w = 9;
|
||||
const size_t suffix_w = canvas_string_width(canvas, hint_suffix);
|
||||
|
||||
const size_t group_w = label_w + 2 + prefix_w + 2 + icon_w + 2 + suffix_w;
|
||||
|
||||
int32_t scroll_offset = 0;
|
||||
if(group_w > row_width) {
|
||||
const size_t overflow = group_w - row_width;
|
||||
const size_t cycle = overflow * 2;
|
||||
const size_t step = cycle ? (model->hint_scroll_tick % cycle) : 0;
|
||||
scroll_offset = (step <= overflow) ? step : (cycle - step);
|
||||
}
|
||||
|
||||
const int32_t draw_x = row_left_x - scroll_offset;
|
||||
canvas_draw_str(canvas, draw_x, text_y, furi_string_get_cstr(disp_str));
|
||||
|
||||
int32_t hint_x = draw_x + label_w + 2;
|
||||
canvas_draw_str(canvas, hint_x, text_y, hint_prefix);
|
||||
hint_x += prefix_w + 2;
|
||||
canvas_draw_icon(canvas, hint_x, icon_y, &I_Ok_btn_9x9);
|
||||
hint_x += icon_w + 2;
|
||||
canvas_draw_str(canvas, hint_x, text_y, hint_suffix);
|
||||
} else {
|
||||
elements_string_fit_width(canvas, disp_str, item_width - (6 * 2));
|
||||
canvas_draw_str(canvas, row_left_x, text_y, furi_string_get_cstr(disp_str));
|
||||
}
|
||||
|
||||
furi_string_free(disp_str);
|
||||
}
|
||||
@@ -338,6 +437,39 @@ void ptt_menu_process_ok(HidPushToTalkMenu* hid_ptt_menu) {
|
||||
}
|
||||
}
|
||||
|
||||
void ptt_menu_process_long_ok(HidPushToTalkMenu* hid_ptt_menu) {
|
||||
PushToTalkMenuList* list = NULL;
|
||||
PushToTalkMenuItem* item = NULL;
|
||||
with_view_model(
|
||||
hid_ptt_menu->view,
|
||||
HidPushToTalkMenuModel * model,
|
||||
{
|
||||
list = &model->lists[model->list_position];
|
||||
const size_t items_size = PushToTalkMenuItemArray_size(list->items);
|
||||
if(model->position < items_size) {
|
||||
item = PushToTalkMenuItemArray_get(list->items, model->position);
|
||||
}
|
||||
},
|
||||
false);
|
||||
if(item && list && hid_ptt_menu->long_ok_callback) {
|
||||
hid_ptt_menu->long_ok_callback(
|
||||
hid_ptt_menu->long_ok_callback_context,
|
||||
list->index,
|
||||
list->label,
|
||||
item->index,
|
||||
item->label);
|
||||
}
|
||||
}
|
||||
|
||||
void ptt_menu_set_long_ok_callback(
|
||||
HidPushToTalkMenu* hid_ptt_menu,
|
||||
PushToTalkMenuLongOkCallback callback,
|
||||
void* callback_context) {
|
||||
furi_assert(hid_ptt_menu);
|
||||
hid_ptt_menu->long_ok_callback = callback;
|
||||
hid_ptt_menu->long_ok_callback_context = callback_context;
|
||||
}
|
||||
|
||||
static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
|
||||
furi_assert(context);
|
||||
HidPushToTalkMenu* hid_ptt_menu = context;
|
||||
@@ -375,7 +507,15 @@ static bool hid_ptt_menu_input_callback(InputEvent* event, void* context) {
|
||||
consumed = true;
|
||||
ptt_menu_process_down(hid_ptt_menu);
|
||||
}
|
||||
} else if(event->type == InputTypeLong && event->key == InputKeyOk) {
|
||||
consumed = true;
|
||||
ptt_menu_process_long_ok(hid_ptt_menu);
|
||||
}
|
||||
|
||||
if(event->type != InputTypeRelease) {
|
||||
hid_ptt_menu_mark_interaction(hid_ptt_menu);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
@@ -392,6 +532,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
|
||||
view_allocate_model(hid_ptt_menu->view, ViewModelTypeLocking, sizeof(HidPushToTalkMenuModel));
|
||||
view_set_draw_callback(hid_ptt_menu->view, hid_ptt_menu_draw_callback);
|
||||
view_set_input_callback(hid_ptt_menu->view, hid_ptt_menu_input_callback);
|
||||
view_set_enter_callback(hid_ptt_menu->view, hid_ptt_menu_enter_callback);
|
||||
view_set_exit_callback(hid_ptt_menu->view, hid_ptt_menu_exit_callback);
|
||||
|
||||
hid_ptt_menu->hint_timer =
|
||||
furi_timer_alloc(hid_ptt_menu_hint_timer_callback, FuriTimerTypePeriodic, hid_ptt_menu);
|
||||
|
||||
with_view_model(
|
||||
hid_ptt_menu->view,
|
||||
@@ -400,6 +545,11 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
|
||||
model->lists_count = 0;
|
||||
model->position = 0;
|
||||
model->window_position = 0;
|
||||
model->last_interaction_tick = furi_get_tick();
|
||||
model->hint_list_position = 0;
|
||||
model->hint_item_position = 0;
|
||||
model->hint_visible = false;
|
||||
model->hint_scroll_tick = 0;
|
||||
},
|
||||
true);
|
||||
return hid_ptt_menu;
|
||||
@@ -407,6 +557,8 @@ HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* hid) {
|
||||
|
||||
void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu) {
|
||||
furi_assert(hid_ptt_menu);
|
||||
furi_timer_stop(hid_ptt_menu->hint_timer);
|
||||
furi_timer_free(hid_ptt_menu->hint_timer);
|
||||
with_view_model(
|
||||
hid_ptt_menu->view,
|
||||
HidPushToTalkMenuModel * model,
|
||||
|
||||
@@ -12,6 +12,13 @@ typedef void (*PushToTalkMenuItemCallback)(
|
||||
uint32_t itemIndex,
|
||||
FuriString* itemLabel);
|
||||
|
||||
typedef void (*PushToTalkMenuLongOkCallback)(
|
||||
void* context,
|
||||
uint32_t listIndex,
|
||||
FuriString* listLabel,
|
||||
uint32_t itemIndex,
|
||||
FuriString* itemLabel);
|
||||
|
||||
HidPushToTalkMenu* hid_ptt_menu_alloc(Hid* bt_hid);
|
||||
|
||||
void hid_ptt_menu_free(HidPushToTalkMenu* hid_ptt_menu);
|
||||
@@ -27,3 +34,8 @@ void ptt_menu_add_item_to_list(
|
||||
void* callback_context);
|
||||
|
||||
void ptt_menu_add_list(HidPushToTalkMenu* hid_ptt_menu, const char* label, uint32_t index);
|
||||
|
||||
void ptt_menu_set_long_ok_callback(
|
||||
HidPushToTalkMenu* hid_ptt_menu,
|
||||
PushToTalkMenuLongOkCallback callback,
|
||||
void* callback_context);
|
||||
|
||||
@@ -253,6 +253,14 @@ App(
|
||||
sources=["modules/js_subghz/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_infrared",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="js_infrared_ep",
|
||||
requires=["js_app"],
|
||||
sources=["modules/js_infrared/*.c"],
|
||||
)
|
||||
|
||||
App(
|
||||
appid="js_blebeacon",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
checkSdkFeatures(["infrared-send"]);
|
||||
let infrared = require("infrared");
|
||||
|
||||
print("Sending Samsung32 signal (lowers volume)...");
|
||||
infrared.sendSignal("Samsung32", 0x00000007, 0x0000000b);
|
||||
delay(1000);
|
||||
print("Sending raw signal... (Fujitsu AC)");
|
||||
infrared.sendRawSignal(
|
||||
[
|
||||
3298, 1571, 442, 368, 442, 367, 443, 1180, 442, 370, 440, 1181, 442, 368,
|
||||
442, 367, 442, 368, 442, 1180, 443, 1180, 442, 368, 441, 367, 442, 369, 441,
|
||||
1180, 442, 1180, 443, 368, 442, 369, 441, 368, 442, 368, 442, 368, 442, 368,
|
||||
441, 368, 441, 368, 442, 368, 442, 368, 442, 367, 442, 368, 442, 366, 444,
|
||||
1181, 442, 368, 441, 367, 442, 368, 442, 367, 442, 369, 441, 367, 443, 367,
|
||||
442, 1180, 442, 368, 442, 368, 442, 368, 441, 368, 441, 1180, 442, 1180,
|
||||
442, 1181, 442, 1181, 441, 1181, 442, 1181, 442, 1181, 442, 1181, 442, 369,
|
||||
441, 368, 441, 1182, 442, 367, 443, 367, 443, 367, 442, 368, 442, 1181, 441,
|
||||
369, 441, 369, 441, 369, 441, 1180, 442, 1181, 441, 369, 442, 368, 442, 367,
|
||||
442, 368, 441, 1181, 441, 1183, 440, 368, 442, 368, 441, 369, 441, 1181,
|
||||
442, 368, 442, 367, 443, 1181, 442, 368, 442, 369, 441, 368, 441, 369, 440,
|
||||
368, 442, 367, 443, 367, 442, 367, 442, 367, 442, 368, 442, 369, 441, 369,
|
||||
441, 368, 442, 369, 441, 368, 441, 367, 443, 368, 442, 367, 442, 369, 441,
|
||||
369, 441, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 368,
|
||||
442, 368, 441, 367, 442, 367, 442, 368, 442, 368, 441, 368, 442, 369, 441,
|
||||
368, 441, 367, 442, 368, 441, 368, 442, 368, 442, 368, 442, 368, 442, 367,
|
||||
442, 1181, 442, 367, 442, 368, 441, 1181, 442, 1182, 441, 1181, 442, 1181,
|
||||
442, 1181, 441, 368, 442, 368, 442, 369, 441,
|
||||
],
|
||||
true,
|
||||
{ frequency: 38000, dutyCycle: 0.33 },
|
||||
);
|
||||
delay(1000);
|
||||
print(
|
||||
"Sending raw signal... (Fujitsu AC) with default frequency and duty cycle",
|
||||
);
|
||||
infrared.sendRawSignal([
|
||||
3300, 1596, 416, 362, 448, 363, 446, 1177, 445, 363, 446, 1177, 445, 362, 448,
|
||||
362, 448, 364, 446, 1178, 444, 1207, 415, 362, 448, 362, 448, 363, 447, 1177,
|
||||
445, 1177, 446, 362, 448, 362, 447, 362, 447, 362, 448, 363, 447, 362, 447,
|
||||
363, 447, 363, 447, 363, 446, 363, 446, 362, 447, 362, 447, 363, 446, 1177,
|
||||
445, 363, 447, 364, 446, 362, 448, 363, 447, 363, 446, 362, 447, 362, 448,
|
||||
1175, 447, 363, 447, 364, 446, 362, 448, 362, 448, 1176, 446, 362, 448, 362,
|
||||
448, 363, 446, 362, 448, 362, 448, 363, 447, 1175, 446, 394, 415, 1176, 446,
|
||||
1178, 444, 1174, 449, 1177, 445, 1180, 443, 1179, 443,
|
||||
]);
|
||||
|
||||
print("Success");
|
||||
@@ -277,6 +277,7 @@ static const char* extra_features[] = {
|
||||
"blebeacon",
|
||||
"i2c",
|
||||
"spi",
|
||||
"infrared-send",
|
||||
"subghz",
|
||||
"usbdisk",
|
||||
"vgm",
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
#include "../../js_modules.h"
|
||||
#include <infrared_worker.h>
|
||||
#include <lib/infrared/signal/infrared_signal.h>
|
||||
#include <lib/infrared/worker/infrared_transmit.h>
|
||||
|
||||
#define TAG "JsMath"
|
||||
|
||||
static void ret_bad_args(struct mjs* mjs, const char* error) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "%s", error);
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
void js_send_protocol_signal(struct mjs* mjs) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args < 3 || num_args > 4) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return;
|
||||
}
|
||||
if(!mjs_is_string(mjs_arg(mjs, 0)) || !mjs_is_number(mjs_arg(mjs, 1)) ||
|
||||
!mjs_is_number(mjs_arg(mjs, 2)) || (num_args == 4 && !mjs_is_object(mjs_arg(mjs, 3)))) {
|
||||
ret_bad_args(mjs, "Wrong argument type");
|
||||
return;
|
||||
}
|
||||
bool repeat = false;
|
||||
int times = 1;
|
||||
if(num_args == 4) {
|
||||
mjs_val_t options_obj = mjs_arg(mjs, 3);
|
||||
|
||||
mjs_val_t repeat_val = mjs_get(mjs, options_obj, "repeat", ~0);
|
||||
if(mjs_is_boolean(repeat_val)) {
|
||||
repeat = mjs_get_bool(mjs, repeat_val);
|
||||
} else if(!mjs_is_undefined(repeat_val)) {
|
||||
ret_bad_args(mjs, "Wrong 'repeat' option type");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_val_t times_val = mjs_get(mjs, options_obj, "times", ~0);
|
||||
if(mjs_is_number(times_val)) {
|
||||
times = mjs_get_int(mjs, times_val);
|
||||
} else if(!mjs_is_undefined(times_val)) {
|
||||
ret_bad_args(mjs, "Wrong 'times' option type");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
InfraredMessage message;
|
||||
message.repeat = repeat;
|
||||
mjs_val_t protocol_arg = mjs_arg(mjs, 0);
|
||||
message.protocol = infrared_get_protocol_by_name(mjs_get_cstring(mjs, &protocol_arg));
|
||||
message.address = mjs_get_int(mjs, mjs_arg(mjs, 1));
|
||||
message.command = mjs_get_int(mjs, mjs_arg(mjs, 2));
|
||||
infrared_send(&message, times);
|
||||
}
|
||||
|
||||
void js_send_raw_signal(struct mjs* mjs) {
|
||||
size_t num_args = mjs_nargs(mjs);
|
||||
if(num_args < 1 || num_args > 3) {
|
||||
ret_bad_args(mjs, "Wrong argument count");
|
||||
return;
|
||||
}
|
||||
if(!mjs_is_array(mjs_arg(mjs, 0)) || (num_args > 1 && !mjs_is_boolean(mjs_arg(mjs, 1))) ||
|
||||
(num_args > 2 && !mjs_is_object(mjs_arg(mjs, 2)))) {
|
||||
ret_bad_args(mjs, "Wrong argument type");
|
||||
return;
|
||||
}
|
||||
int array_length = mjs_array_length(mjs, mjs_arg(mjs, 0));
|
||||
uint32_t timings[array_length];
|
||||
for(int i = 0; i < array_length; i++) {
|
||||
mjs_val_t elem = mjs_array_get(mjs, mjs_arg(mjs, 0), i);
|
||||
if(!mjs_is_number(elem)) {
|
||||
ret_bad_args(mjs, "Timings array must contain only numbers");
|
||||
return;
|
||||
}
|
||||
timings[i] = mjs_get_int(mjs, elem);
|
||||
}
|
||||
|
||||
bool start_from_mark = true;
|
||||
if(num_args > 1) {
|
||||
start_from_mark = mjs_get_bool(mjs, mjs_arg(mjs, 1));
|
||||
}
|
||||
|
||||
if(num_args > 2) {
|
||||
mjs_val_t options_obj = mjs_arg(mjs, 2);
|
||||
|
||||
mjs_val_t frequency_val = mjs_get(mjs, options_obj, "frequency", ~0);
|
||||
if(!mjs_is_number(frequency_val)) {
|
||||
ret_bad_args(mjs, "Wrong 'frequency' option type");
|
||||
return;
|
||||
}
|
||||
|
||||
mjs_val_t duty_val = mjs_get(mjs, options_obj, "dutyCycle", ~0);
|
||||
if(!mjs_is_number(duty_val)) {
|
||||
ret_bad_args(mjs, "Wrong 'dutyCycle' option type");
|
||||
return;
|
||||
}
|
||||
uint32_t frequency = mjs_get_int(mjs, frequency_val);
|
||||
float duty_cycle = mjs_get_double(mjs, duty_val);
|
||||
infrared_send_raw_ext(timings, array_length, start_from_mark, frequency, duty_cycle);
|
||||
} else {
|
||||
infrared_send_raw(timings, array_length, start_from_mark);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void* js_infrared_create(struct mjs* mjs, mjs_val_t* object, JsModules* modules) {
|
||||
UNUSED(modules);
|
||||
mjs_val_t infrared_object = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, infrared_object, "sendSignal", ~0, MJS_MK_FN(js_send_protocol_signal));
|
||||
mjs_set(mjs, infrared_object, "sendRawSignal", ~0, MJS_MK_FN(js_send_raw_signal));
|
||||
*object = infrared_object;
|
||||
return (void*)1;
|
||||
}
|
||||
|
||||
static const JsModuleDescriptor js_infrared_desc = {
|
||||
"infrared",
|
||||
js_infrared_create,
|
||||
NULL,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor plugin_descriptor = {
|
||||
.appid = PLUGIN_APP_ID,
|
||||
.ep_api_version = PLUGIN_API_VERSION,
|
||||
.entry_point = &js_infrared_desc,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* js_infrared_ep(void) {
|
||||
return &plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Module for using Infrared blaster/receptor
|
||||
* @version Available with JS feature `infrared-send`
|
||||
* @module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sends an IR signal using a known protocol by Flipper Firmware
|
||||
* @param address Note that the address expects a number. If you're reading from Flipper's IR files, the address is usually in little-endian hex format. Javascript numbers are defined as big endian by default.
|
||||
* @param command Note that the command expects a number. If you're reading from Flipper's IR files, the command is usually in little-endian hex format. Javascript numbers are defined as big endian by default.
|
||||
* @param options Repeat marks the signal as a repeat signal, times indicates how many times to send the signal
|
||||
*/
|
||||
export declare function sendSignal(
|
||||
protocol:
|
||||
| "NEC"
|
||||
| "NECext"
|
||||
| "NEC42"
|
||||
| "NEC42ext"
|
||||
| "Samsung32"
|
||||
| "RC6"
|
||||
| "RC5"
|
||||
| "RC5X"
|
||||
| "SIRC"
|
||||
| "SIRC15"
|
||||
| "SIRC20"
|
||||
| "Kaseikyo"
|
||||
| "RCA",
|
||||
address: number,
|
||||
command: number,
|
||||
options?: { repeat?: boolean; times?: number },
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Sends a signal from an unknown protocol
|
||||
* @param startFromMark defaults to true
|
||||
*/
|
||||
export declare function sendRawSignal(
|
||||
timings: number[],
|
||||
startFromMark?: boolean,
|
||||
advancedSettings?: { frequency: number; dutyCycle: number },
|
||||
): void;
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@next-flip/fz-sdk-mntm",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"description": "Type declarations and documentation for native JS modules available on Momentum Custom Firmware for Flipper Zero",
|
||||
"keywords": [
|
||||
"momentum",
|
||||
|
||||
@@ -153,10 +153,11 @@ async function upload(config) {
|
||||
port.write(`storage remove ${config.output}\x0d`);
|
||||
port.drain();
|
||||
await waitFor(">: ", 1000);
|
||||
port.write(`storage write_chunk ${config.output} ${appFile.length}\x0d`);
|
||||
const appFileBuffer = Buffer.from(appFile, "utf8");
|
||||
port.write(`storage write_chunk ${config.output} ${appFileBuffer.length}\x0d`);
|
||||
await waitFor("Ready", 1000);
|
||||
port.write(appFile);
|
||||
port.drain();
|
||||
port.write(appFileBuffer);
|
||||
await new Promise(resolve => port.drain(resolve));
|
||||
await waitFor(">: ", 1000);
|
||||
|
||||
console.log("Launching application");
|
||||
|
||||
|
Before Width: | Height: | Size: 858 B |
|
Before Width: | Height: | Size: 855 B |
|
Before Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 861 B |
|
Before Width: | Height: | Size: 853 B |
|
Before Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 852 B |
|
Before Width: | Height: | Size: 856 B |
|
Before Width: | Height: | Size: 850 B |
|
Before Width: | Height: | Size: 851 B |
|
Before Width: | Height: | Size: 860 B |