diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f94135d8..12ed917e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c index 032fe115e..f39b8e2ee 100644 --- a/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c +++ b/applications/debug/unit_tests/tests/lfrfid/lfrfid_protocols.c @@ -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); } diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 43111f9ad..c3e409c02 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -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; diff --git a/applications/external b/applications/external index e9230bc08..b05485f7d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit e9230bc089ae26094a9cba4614815c36532fc964 +Subproject commit b05485f7d13ee1595d06745c881f1d3aadb3d45d diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 89dbcf42c..03481ffe5 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -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", diff --git a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c index 2f968b58e..3976dfe1b 100644 --- a/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.c +++ b/applications/main/nfc/cli/commands/dump/nfc_cli_command_dump.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 #include @@ -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) { diff --git a/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c b/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c new file mode 100644 index 000000000..04a29e79a --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c @@ -0,0 +1,31 @@ +#include "nfc_cli_dump_ntag4xx.h" +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.h b/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.h new file mode 100644 index 000000000..3f0738452 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_cli_dump_common_types.h" + +NfcCommand nfc_cli_dump_poller_callback_ntag4xx(NfcGenericEvent event, void* context); diff --git a/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c b/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c new file mode 100644 index 000000000..fd955f69e --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c @@ -0,0 +1,31 @@ +#include "nfc_cli_dump_type_4_tag.h" +#include + +#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; +} diff --git a/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.h b/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.h new file mode 100644 index 000000000..2492ad5f5 --- /dev/null +++ b/applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.h @@ -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); diff --git a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c index abe153938..c761c3d6e 100644 --- a/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c +++ b/applications/main/nfc/cli/commands/helpers/nfc_cli_format.c @@ -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) { diff --git a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c index f90adb4c6..03fc9eec8 100644 --- a/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c +++ b/applications/main/nfc/cli/commands/raw/nfc_cli_command_raw.c @@ -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) { diff --git a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c index 4c69fbe81..4451751f9 100644 --- a/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c +++ b/applications/main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c @@ -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, }, }; diff --git a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c index 5a0c88cf7..642f9b460 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c +++ b/applications/main/nfc/helpers/protocol_support/mf_ultralight/mf_ultralight.c @@ -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 = { diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 32094140d..020323bc2 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -9,6 +9,10 @@ #include "../nfc_protocol_support_common.h" #include "../nfc_protocol_support_gui_common.h" +#include + +#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, }, }; diff --git a/applications/main/nfc/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index c03415cdd..5a84a9acc 100644 --- a/applications/main/nfc/nfc_app_i.h +++ b/applications/main/nfc/nfc_app_i.h @@ -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; diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index add7ab560..a9cf2c946 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -1,23 +1,21 @@ +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! + #include "nfc_supported_card_plugin.h" - #include - #include #include #include #include - -#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 +#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; } diff --git a/applications/main/nfc/plugins/supported_cards/sevppk_tk.c b/applications/main/nfc/plugins/supported_cards/sevppk_tk.c new file mode 100644 index 000000000..e069c8c9a --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sevppk_tk.c @@ -0,0 +1,399 @@ +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/applications/main/nfc/plugins/supported_cards/sk_tk.c b/applications/main/nfc/plugins/supported_cards/sk_tk.c new file mode 100644 index 000000000..a294ed45d --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sk_tk.c @@ -0,0 +1,406 @@ +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/applications/main/nfc/plugins/supported_cards/szppk_so.c b/applications/main/nfc/plugins/supported_cards/szppk_so.c new file mode 100644 index 000000000..16bbbd807 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/szppk_so.c @@ -0,0 +1,449 @@ +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! + +#include "nfc_supported_card_plugin.h" +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/applications/main/nfc/plugins/supported_cards/two_cities.c b/applications/main/nfc/plugins/supported_cards/two_cities.c index 240c6c585..1295b1c65 100644 --- a/applications/main/nfc/plugins/supported_cards/two_cities.c +++ b/applications/main/nfc/plugins/supported_cards/two_cities.c @@ -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); diff --git a/applications/main/nfc/resources/nfc/assets/sev_id.nfc b/applications/main/nfc/resources/nfc/assets/sev_id.nfc new file mode 100644 index 000000000..35a2bc234 --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/sev_id.nfc @@ -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 \ No newline at end of file diff --git a/applications/main/nfc/resources/nfc/assets/sk_id.nfc b/applications/main/nfc/resources/nfc/assets/sk_id.nfc new file mode 100644 index 000000000..db840ad21 --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/sk_id.nfc @@ -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 diff --git a/applications/main/nfc/resources/nfc/assets/sz_id.nfc b/applications/main/nfc/resources/nfc/assets/sz_id.nfc new file mode 100644 index 000000000..98c6f287b --- /dev/null +++ b/applications/main/nfc/resources/nfc/assets/sz_id.nfc @@ -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 \ No newline at end of file diff --git a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index 843261142..ed9307759 100644 --- a/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c +++ b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c @@ -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; diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index fab4ec3e8..d80978b8e 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -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, ) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index fa2c8d9be..963fd4503 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -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, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index a0f07a1d8..7feaea887 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -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, diff --git a/applications/main/subghz/helpers/subghz_gen_info.h b/applications/main/subghz/helpers/subghz_gen_info.h index e47c218d6..8fcfb92fc 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -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; diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 361505505..4a7d6bd77 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -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; } diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index 8687607c9..7074a30bd 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -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); diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index fc6cb4fb9..4721566af 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -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, diff --git a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h index afc1059b5..d9d8bbb0a 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -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, diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index c4c36e220..9a965d6de 100644 --- a/applications/main/subghz/helpers/subghz_types.h +++ b/applications/main/subghz/helpers/subghz_types.h @@ -10,6 +10,7 @@ typedef enum { SubGhzNotificationStateTx, SubGhzNotificationStateRx, SubGhzNotificationStateRxDone, + SubGhzNotificationStateTxWait, } SubGhzNotificationState; /** SubGhzTxRx state */ diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index b55548844..2772b0e39 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -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 diff --git a/applications/main/subghz/scenes/subghz_scene_config.h b/applications/main/subghz/scenes/subghz_scene_config.h index 0e95fd261..c855a9b2b 100644 --- a/applications/main/subghz/scenes/subghz_scene_config.h +++ b/applications/main/subghz/scenes/subghz_scene_config.h @@ -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) diff --git a/applications/main/subghz/scenes/subghz_scene_more_raw.c b/applications/main/subghz/scenes/subghz_scene_more_raw.c index 95d586934..fb2ae5085 100644 --- a/applications/main/subghz/scenes/subghz_scene_more_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_more_raw.c @@ -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; diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 995c2c096..d553fef3d 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -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", diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index a52a9cde0..ad7cb6cdf 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -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); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index ce824fed0..65dfd475e 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -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); diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 85d0d52ac..6c1d79afc 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -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; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 2f242d075..ad012e6c4 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -2,6 +2,9 @@ #include +#include "applications/main/subghz/helpers/subghz_txrx_i.h" +#include + #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; } diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index b262679a4..f3753a3bc 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -1,6 +1,8 @@ #include "../subghz_i.h" #include +#include +#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; } diff --git a/applications/main/subghz/scenes/subghz_scene_save_name.c b/applications/main/subghz/scenes/subghz_scene_save_name.c index fed134512..5589ad4b5 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_name.c +++ b/applications/main/subghz/scenes/subghz_scene_save_name.c @@ -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"); diff --git a/applications/main/subghz/scenes/subghz_scene_save_success.c b/applications/main/subghz/scenes/subghz_scene_save_success.c index 9a71c74a7..8c316981d 100644 --- a/applications/main/subghz/scenes/subghz_scene_save_success.c +++ b/applications/main/subghz/scenes/subghz_scene_save_success.c @@ -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; } } diff --git a/applications/main/subghz/scenes/subghz_scene_saved_menu.c b/applications/main/subghz/scenes/subghz_scene_saved_menu.c index 949fb871a..386d4d005 100644 --- a/applications/main/subghz/scenes/subghz_scene_saved_menu.c +++ b/applications/main/subghz/scenes/subghz_scene_saved_menu.c @@ -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; } diff --git a/applications/main/subghz/scenes/subghz_scene_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index e8914dbc5..efec4e4a3 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_button.c +++ b/applications/main/subghz/scenes/subghz_scene_set_button.c @@ -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; diff --git a/applications/main/subghz/scenes/subghz_scene_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index a58749256..950e77c6a 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -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); } } diff --git a/applications/main/subghz/scenes/subghz_scene_set_key.c b/applications/main/subghz/scenes/subghz_scene_set_key.c index 690c62182..a760e8ffa 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_key.c +++ b/applications/main/subghz/scenes/subghz_scene_set_key.c @@ -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); } } diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index 648cfa1d3..580d2de0d 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -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); } } diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index 9219842e5..9d4640d00 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_serial.c +++ b/applications/main/subghz/scenes/subghz_scene_set_serial.c @@ -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); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 28cd4bcf7..4b84b1066 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -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) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index e74219a28..f4635844f 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -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); diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 6046b2c6d..9816562f0 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -1,6 +1,7 @@ #include "../subghz_i.h" #include "subghz_scene_start.h" #include +#include #include @@ -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; } diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 0702f1d0a..16ba681c8 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -5,6 +5,9 @@ #include +#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; } diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index 64209a051..9b0507bf5 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -8,6 +8,8 @@ #include #include +#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 diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 697c9ff5b..9c8df6fb7 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -5,7 +5,8 @@ #include #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"); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index d8cf5240a..0dcd4d87b 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -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)) { diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 3f858c780..f7c6304e2 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -103,6 +103,7 @@ struct SubGhz { bool fav_timeout; FuriTimer* timer; + uint8_t tx_power; void* rpc_ctx; }; diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index 87b881d63..f4f986159 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -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); diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 457792175..9a578a1c0 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -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); diff --git a/applications/main/subghz/views/receiver.c b/applications/main/subghz/views/receiver.c index 47772ac2d..960cbd38c 100644 --- a/applications/main/subghz/views/receiver.c +++ b/applications/main/subghz/views/receiver.c @@ -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; diff --git a/applications/main/subghz/views/subghz_read_raw.c b/applications/main/subghz/views/subghz_read_raw.c index c9c01e2ec..5571e7780 100644 --- a/applications/main/subghz/views/subghz_read_raw.c +++ b/applications/main/subghz/views/subghz_read_raw.c @@ -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; diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 9ecf33687..59bf4eef7 100644 --- a/applications/services/gui/canvas.c +++ b/applications/services/gui/canvas.c @@ -1,4 +1,5 @@ #include "canvas_i.h" +#include "canvas.h" #include "icon_animation_i.h" #include diff --git a/applications/services/gui/canvas.h b/applications/services/gui/canvas.h index f568be7eb..fc46ec752 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.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 diff --git a/applications/services/gui/canvas_i.h b/applications/services/gui/canvas_i.h index cc918f8df..30c96b45d 100644 --- a/applications/services/gui/canvas_i.h +++ b/applications/services/gui/canvas_i.h @@ -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 diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 638e4cfbe..81475f34f 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -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 diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c index c07ab1ae6..a4a12caa1 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -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; } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c index 2f8644761..db63ba631 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark_confirm.c @@ -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); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c index 0f8e1aa96..53afaf634 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_factory_reset.c @@ -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++; diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c index c79647ad2..12cb702d6 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_format_confirm.c @@ -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); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c index d1652fa20..b7b011f41 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -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) { diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c index 87a7ac655..b7e332e47 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_internal_info.c @@ -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; } } diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c index a24e2c282..a81951c56 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_sd_info.c @@ -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); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c index 1b9970f9f..e9acb7b4b 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmount_confirm.c @@ -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); diff --git a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c index 86398b1c9..6a3d98604 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -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) { diff --git a/applications/settings/storage_settings/storage_settings.c b/applications/settings/storage_settings/storage_settings.c index 3808c42e2..da80199a8 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -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); diff --git a/applications/settings/storage_settings/storage_settings.h b/applications/settings/storage_settings/storage_settings.h index 5f4c6404e..a0f7a81b4 100644 --- a/applications/settings/storage_settings/storage_settings.h +++ b/applications/settings/storage_settings/storage_settings.h @@ -44,6 +44,8 @@ typedef struct { // helpers SubmenuSettingsHelperDescriptor* helper_descriptor; SubmenuSettingsHelper* settings_helper; + + bool from_favorites; } StorageSettings; typedef enum { diff --git a/applications/system/hid_app/assets/for_11x5.png b/applications/system/hid_app/assets/for_11x5.png new file mode 100644 index 000000000..42495d203 Binary files /dev/null and b/applications/system/hid_app/assets/for_11x5.png differ diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 8a28cd292..9670b7c2a 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -1,19 +1,60 @@ #include "hid_ptt.h" #include "hid_ptt_menu.h" #include +#include #include #include +#include +#include #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, diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 074c85ba7..ca293160c 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -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, diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h index 2defd0569..ee61a9a50 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.h +++ b/applications/system/hid_app/views/hid_ptt_menu.h @@ -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); diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 855ad678b..34af4a45b 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -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, diff --git a/applications/system/js_app/examples/apps/Scripts/Examples/infrared-send.js b/applications/system/js_app/examples/apps/Scripts/Examples/infrared-send.js new file mode 100644 index 000000000..6606ef5f8 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/Examples/infrared-send.js @@ -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"); diff --git a/applications/system/js_app/js_modules.c b/applications/system/js_app/js_modules.c index cdfca67af..c689d0f3b 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -277,6 +277,7 @@ static const char* extra_features[] = { "blebeacon", "i2c", "spi", + "infrared-send", "subghz", "usbdisk", "vgm", diff --git a/applications/system/js_app/modules/js_infrared/js_infrared.c b/applications/system/js_app/modules/js_infrared/js_infrared.c new file mode 100644 index 000000000..9015a98ea --- /dev/null +++ b/applications/system/js_app/modules/js_infrared/js_infrared.c @@ -0,0 +1,129 @@ +#include "../../js_modules.h" +#include +#include +#include + +#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; +} diff --git a/applications/system/js_app/packages/fz-sdk/infrared/index.d.ts b/applications/system/js_app/packages/fz-sdk/infrared/index.d.ts new file mode 100644 index 000000000..4767167f1 --- /dev/null +++ b/applications/system/js_app/packages/fz-sdk/infrared/index.d.ts @@ -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; diff --git a/applications/system/js_app/packages/fz-sdk/package.json b/applications/system/js_app/packages/fz-sdk/package.json index e0ea053f3..2c460f0f7 100644 --- a/applications/system/js_app/packages/fz-sdk/package.json +++ b/applications/system/js_app/packages/fz-sdk/package.json @@ -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", diff --git a/applications/system/js_app/packages/fz-sdk/sdk.js b/applications/system/js_app/packages/fz-sdk/sdk.js index 34279270b..2ddec2d08 100644 --- a/applications/system/js_app/packages/fz-sdk/sdk.js +++ b/applications/system/js_app/packages/fz-sdk/sdk.js @@ -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"); diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png deleted file mode 100755 index f1207ed14..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png deleted file mode 100755 index 9d9012281..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png deleted file mode 100755 index cb8f173b0..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png deleted file mode 100755 index 0b042e3aa..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png deleted file mode 100755 index 5d4c7e7c5..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png deleted file mode 100755 index 35ee06ee0..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png deleted file mode 100755 index 3a47dfc17..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png deleted file mode 100755 index 0b3bb3250..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png deleted file mode 100755 index 233448547..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png deleted file mode 100755 index b7164380d..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png deleted file mode 100755 index da3a78f4c..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png deleted file mode 100755 index adbe53159..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png b/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png deleted file mode 100755 index 3d81f935a..000000000 Binary files a/assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt b/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt deleted file mode 100755 index a2c733397..000000000 --- a/assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt +++ /dev/null @@ -1,23 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 10 -Active frames: 18 -Frames order: 0 1 2 1 0 1 2 1 0 1 2 3 4 5 6 5 4 7 2 8 9 10 11 10 9 10 11 12 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 11 -Y: 19 -Text: HAPPY\nHOLIDAYS! -AlignH: Right -AlignV: Center -StartFrame: 22 -EndFrame: 27 diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png deleted file mode 100755 index 0e86e6641..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png deleted file mode 100755 index 219407e4b..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png deleted file mode 100755 index 5459ae27c..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png deleted file mode 100755 index 9f9d80de6..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png deleted file mode 100755 index 7b35e1d3c..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png deleted file mode 100755 index 9e560baa8..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png deleted file mode 100755 index a5d6c1a7a..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png deleted file mode 100755 index 57b2c9bbe..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png deleted file mode 100755 index 4be832c64..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png deleted file mode 100755 index 1910cf939..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png deleted file mode 100755 index 0236692c6..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png deleted file mode 100755 index 7450c87b4..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png deleted file mode 100755 index 0355c510c..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png deleted file mode 100755 index c371e217f..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png deleted file mode 100755 index 822d2230e..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png deleted file mode 100755 index 6d359ef86..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png deleted file mode 100755 index 639834612..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png deleted file mode 100755 index fbafab70f..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png deleted file mode 100755 index 190985282..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png deleted file mode 100755 index 894e62d29..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png deleted file mode 100755 index 962349bfd..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png deleted file mode 100755 index 2eb4456cc..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png deleted file mode 100755 index ee325d3ee..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png deleted file mode 100755 index e5772a672..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png deleted file mode 100755 index 11fc5d0b4..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png deleted file mode 100755 index ec47b899c..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png deleted file mode 100755 index 3f345b563..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png deleted file mode 100755 index c044765eb..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png deleted file mode 100755 index 44c4d0d49..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png deleted file mode 100755 index f70ecb3ab..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png deleted file mode 100755 index 9af3a8338..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png deleted file mode 100755 index 7d970234f..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png deleted file mode 100755 index 70eab1f3e..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png deleted file mode 100755 index 8169e0766..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png deleted file mode 100755 index adee1a63d..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png deleted file mode 100755 index db6e667fb..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png b/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png deleted file mode 100755 index a6a37fe29..000000000 Binary files a/assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png and /dev/null differ diff --git a/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt b/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt deleted file mode 100755 index 3e31e1d69..000000000 --- a/assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt +++ /dev/null @@ -1,23 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 18 -Active frames: 19 -Frames order: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 -Active cycles: 1 -Frame rate: 2 -Duration: 3600 -Active cooldown: 7 - -Bubble slots: 1 - -Slot: 0 -X: 21 -Y: 25 -Text: AAAAaAAAAHHh!! -AlignH: Right -AlignV: Bottom -StartFrame: 30 -EndFrame: 32 diff --git a/assets/dolphin/external/manifest.txt b/assets/dolphin/external/manifest.txt index 0c2642a5b..e1e3ca1f0 100644 --- a/assets/dolphin/external/manifest.txt +++ b/assets/dolphin/external/manifest.txt @@ -245,17 +245,3 @@ Max butthurt: 13 Min level: 9 Max level: 30 Weight: 4 - -Name: L1_Happy_holidays_128x64 -Min butthurt: 0 -Max butthurt: 14 -Min level: 1 -Max level: 30 -Weight: 3 - -Name: L1_Sleigh_ride_128x64 -Min butthurt: 0 -Max butthurt: 14 -Min level: 1 -Max level: 30 -Weight: 4 diff --git a/documentation/SubGHzCounterMode.md b/documentation/SubGHzCounterMode.md index cac4dc0a0..699aff060 100644 --- a/documentation/SubGHzCounterMode.md +++ b/documentation/SubGHzCounterMode.md @@ -129,6 +129,30 @@ CounterMode: 1 **Mode 6:** - Counter freeze - do not increment +**Mode 7:** +- Incremental mode: `+0x3333` 5 times to current counter and return original value back adding +1 - 2 times - 7 signals in pack total +- Might work with Doorhan, seen in some "universal remotes" +- One click of Send button on flipper may bypass receiver counter, wait for full transmission + + +--- + +### 5. V2 Phoenix (Phox) + +**Mode 0 (Default):** +- Standard - acts like regular remote +- Uses rolling counter multiplier from global settings (default +1) +- Counter increments based on the multiplier value (default +1) +- Resets to 0 when overflow occurs (> 0xFFFF) + +**Mode 1 (ofex like):** +- Counter sequence: `0x0000 / 0x0001 / 0xFFFE / 0xFFFF` +- Verified as working + +**Mode 2 (0 - 4):** +- Counter sequence: `0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004` +- Might work (let us know!) + --- ## Notes and Warnings diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 2351531e9..bef228634 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -24,11 +24,13 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - AN-Motors (Alutech) AT4 `433.92MHz` `AM650` (64 bits, Pseudo-Dynamic, KeeLoq based) - Ansonic `433MHz` `FM` (12 bits, Static) - BETT `433.92MHz` `AM650` (18 bits, Static) -- Beninca ARC (TOGO2VA) `433.92MHz` `AM650` (128 bits, Dynamic AES) -- BFT Mitto `433.92MHz` `AM650` (64 bits, Dynamic, KeeLoq based with Seed) -- CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) -- CAME TWEE `433.92MHz` `AM650` (54 bits, Static) +- Beninca ARC (TOGO2VA) `433.92MHz` `AM650` (128 bits, Dynamic AES128) (button code `0` emulates `hidden button` option on the remote) +- BFT Mitto `433.92MHz` `AM650` (64 bits, Dynamic, KeeLoq based with Seed taken from serial) +- Erreka - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - secure learning with Seed) +- CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) (TOPD4REN, TOP44RBN, TOP42R, TOP44R) +- CAME TWEE `433.92MHz` `AM650` (54 bits, Pseudo-Dynamic) (+ TOP44FGN) (aka New Fixed Code) - CAME `433.92MHz, 868MHz` `AM650` (12, 24 bits, Static) +- Ditec GOL4 `433.92MHz` `AM650` (54 bits, Dynamic) (should be compatible with BIXLG4, BIXLS2, BIXLP2) - (right arrow emulates button `0` (hidden button)) - Prastel `433.92MHz, 868MHz` `AM650` (25, 42 bits, Static) - Airforce `433.92MHz, 868MHz` `AM650` (18 bits, Static) - Chamberlain Code `AM650` (10 bits, Static) @@ -36,30 +38,32 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - Dickert MAHS `AM650` (36 bits, Static) - Doitrand `AM650` (37 bits, Dynamic) - Elplast/P-11B/3BK/E.C.A `433MHz` `AM650` (18 bits, Static) -- FAAC SLH `433.92MHz, 868MHz` `AM650` (64 bits, Dynamic) +- FAAC SLH `433.92MHz, 868.35MHz` `AM650` (64 bits, Dynamic) (+ Genius KILO TX2/4 JLC, other Genius models) - Gate TX `433.92MHz` `AM650` (64 bits, Static) - Hormann `868MHz` `AM650` (44 bits, Static) - HCS101 `AM650` (64 bits, Simple Dynamic, KeeLoq-like) -- IDO `433MHz` `AM650` (48 bits, Dynamic) +- iDO `433MHz` `AM650` (48 bits, Dynamic) (Decode only) - KingGates Stylo 4k `433.92MHz` `AM650` (89 bits, Dynamic, KeeLoq based) - Mastercode `AM650` (36 bits, Static) - Megacode `AM650` (24 bits, Static) +- Nord ICE `433.92MHz` `AM650` (33 bits, Static) - Nero Sketch `AM650` (40 bits, Static) - Nice Flo `433.92MHz` `AM650` (12, 24 bits, Static) - Nice FloR-S `433.92MHz` `AM650` (52 bits, Dynamic) - Nice One `433.92MHz` `AM650` (72 bits, Dynamic) - Revers RB2 (Реверс РБ-2 (М)) `433.92MHz` `AM650` (64 bits, Static) - Roger `433.92MHz` `AM650` (28 bits, Static) -- V2 Phoenix (Phox) `433.92MHz` `AM650` (52 bits, Dynamic) +- V2 Phoenix (Phox) `433.92MHz` `AM650` (52 bits, Dynamic) (receivers have option to enable Static mode, making them ignore rolling part of the key) - Marantec `433.92MHz, 868MHz` `AM650` (49 bits, Static) - Marantec24 `868MHz` `AM650` (24 bits, Static) -- Somfy Keytis `433.92MHz, 868MHz` `AM650` (80 bits, Dynamic) +- Somfy Keytis `433.42MHz, 868MHz` `AM650` (80 bits, Dynamic) (KeyGo 4 RTS 4 / Keytis NS 2RTS) - ZKTeco `430.5MHz` `AM650` (24 bits, Static - Princeton based) - (Button codes (already mapped to arrow keys): `0x30 (UP)`, `0x03 (STOP)`, `0x0C (DOWN)`) - Linear `300MHz` `AM650` (10 bits, Static) - Linear Delta3 `AM650` (8 bits, Static) - Nero Radio `434.42MHz` `AM650` (56 bits, Static mode only, Dynamic is unsupported) - Security+1.0 `315MHz, 433.92MHz, 390MHz` `AM650` (42 bits, Dynamic) - Security+2.0 `310MHz, 390MHz, 868MHz` `AM650` (62 bits, Dynamic) +- Allstar Firefly 318ALD31K `318MHz` `AM650` (18 bits, Static) ### Sensors & Smart home - Intertechno V3 `AM650` (32 bits, Static) - Lights, sockets, other. @@ -68,9 +72,10 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - Legrand `AM650` (18 bits, Static) - Doorbells - Somfy Telis `433.92MHz` `AM650` (56 bits, Dynamic) - Feron `433.92MHz` `AM650` (32 bits, Static) - RGB LED remotes, other. -- Honeywell `AM650` (64 bits, Dynamic) - Alarm, Sensor -- Honeywell WDB `AM650` (48 bits, Dynamic) - Doorbell +- Honeywell `AM650` (64 bits, Static) - Alarm, Sensor +- Honeywell WDB `AM650` (48 bits, Static) - Doorbell - Magellan `433.92MHz` `AM650` (32 bits, Static) - Sensor, alarm +- Jarolift `433.92MHz` `AM650` (72 bits, Dynamic, KeeLoq based) - Automatic roller shutters ### Alarms - Hollarm `433.92MHz` `AM650` (42 bits, Static) - Bike alarms @@ -84,6 +89,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - SMC5326 `315MHz, 433.92MHz, Any other frequency` `AM650` (25 bits, Static) - Hay21 `433.92MHz` `AM650` (21 bits, Dynamic) - Treadmill37 (QH-433) `433.92MHz` `AM650` (37 bits, Static) +- KeyFinder `433.92MHz` `AM650` (24 bits, Static) --- @@ -101,15 +107,15 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - Aprimatic - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial number art in Hop + 2bit "parity" in front of it replacing first 2 bits of serial) - Beninca - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (no serial part in Hop - magic XOR) - CAME Space - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning) -- Cardin S449 - `433.92MHz` `FSK12K` (KeeLoq, 64 bits) (12bit (original remotes) or 10bit (chinese remotes) serial part in Hop - normal learning) +- Cardin S449 - `433.92MHz` `FSK12K` (KeeLoq, 64 bits) (12bit (original remotes) or 10bit (chinese remotes) serial part in Hop - normal learning) (receiver checks for 10bit only (unverified)) - Centurion - `433.92MHz` `AM650` (KeeLoq, 64 bits) (no serial in Hop, uses fixed value 0x1CE - normal learning) - Comunello - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (normal learning) - DEA Mio - `433.92MHz` `AM650` (KeeLoq, 64 bits) (modified serial in Hop, uses last 3 digits modifying first one (example - 419 -> C19) - simple learning) - DoorHan - 315MHz, `433.92MHz` `AM650` (KeeLoq, 64 bits) - DTM Neo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning) -- Elmes Poland - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning) +- Elmes Poland - `303, 433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning) - FAAC RC,XT - `433.92MHz, 868MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) -- Genius Bravo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- Genius Bravo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (Genius ECHO, Genius Bravo (Button code 0xB for prog. mode)) - Gibidi - `433.92MHz` `AM650` (KeeLoq, 64 bits) - GSN - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) - Hormann EcoStar - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning) @@ -120,14 +126,54 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - KEY (KeeLoq, 64 bits) - Monarch - `433.92MHz` `AM650` (KeeLoq, 64 bits) (no serial in Hop, uses fixed value 0x100 - normal learning) - Motorline - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning) -- Mutanco/Mutancode (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- Clemsa Mutancode (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) - Mhouse - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) - Nice Smilo - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) - Normstahl - `433.92MHz` `AM650` (KeeLoq, 64 bits) - Novoferm - `433.92MHz` `AM650` (KeeLoq, 64 bits) - Sommer `434.42MHz, 868.80MHz` `FSK12K (or FSK476)` (KeeLoq, 64 bits) (normal learning) (TX03-868-4, Pearl, and maybe other models are supported (SOMloq)) - Steelmate - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) -- Stilmatic - `433.92MHz` `AM650` (KeeLoq, 64 bits) (normal learning) +- Stilmatic (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (receiver checks for 10bit only (unverified)) +- Pujol - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - special learning) (Pujol Vario 4, other models) +- Pujol Vario - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) (older models) +- Wisniowski (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- Doormatic (KeeLoq, 64 bits) (normal learning) +- Elvox (KeeLoq, 64 bits) (normal learning) +- ET Blue (KeeLoq, 64 bits) (10bit serial part in Hop - normal learning) +- ET Blue Mix (KeeLoq, 64 bits) (10bit serial part in Hop - normal learning) +- ATA PTX4 (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- Verex (KeeLoq, 64 bits) (normal learning) +- Mc Garcia (KeeLoq, 64 bits) (10bit serial part in Hop - simple learning) +- Fadini (KeeLoq, 64 bits) (12bit serial part in Hop - simple learning) +- Seav (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- AERF Otros (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Collbaix (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Hy dom (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Medva (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF VDS (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Temp (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Sabutom (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- AERF Motorgate (KeeLoq, 64 bits) (10bit serial part in Hop - special learning) +- JCM1G EMFA (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G DMIL (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Antonio Meca (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Hy dom (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Baleato (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Cas (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Cubells (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Cyacsa (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Forsa (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Gandara (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Pujol (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Gibidi (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Gibidi2 (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Hybrid (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Tech (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Norton (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Nueva Castilla (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Puertas Lorenzo (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Serviparking (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) +- JCM1G Zibor (KeeLoq, 64 bits) (8bit serial part in Hop - simple learning) ### Alarms, unknown origin, etc. - APS-1100/APS-2550 (KeeLoq, 64 bits) @@ -163,6 +209,11 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - Tomahawk TZ-9030 (KeeLoq, 64 bits) - Tomahawk Z,X 3-5 (KeeLoq, 64 bits) - ZX-730-750-1055 (KeeLoq, 64 bits) +- Zero Simple (KeeLoq, 64 bits) +- Zero Normal (KeeLoq, 64 bits) +- FFFF Simple (KeeLoq, 64 bits) +- FFFF Normal (KeeLoq, 64 bits) +- Miserere (KeeLoq, 64 bits) (normal learning) *Note: Most KeeLoq manufacturers operate in the 433 MHz and 868 MHz frequency bands with AM650 modulation. Some operate at other frequencies or modulations. Not all KeeLoq systems are supported for full decoding or emulation.* diff --git a/firmware.scons b/firmware.scons index e53fd8edf..cb6e47e22 100644 --- a/firmware.scons +++ b/firmware.scons @@ -67,7 +67,7 @@ env = ENV.Clone( }, "flipper_application": { "CCFLAGS": [ - "-Og", + "-Os", ], "CPPDEFINES": [ "NDEBUG", @@ -216,6 +216,14 @@ sources.extend( ) ) +# Link-Time Optimization for firmware dfu only, we are desperately out of flash space +# if ENV["COMPACT"]: +# fwenv.Append( +# CCFLAGS=[ +# "-flto", +# ], +# ) + # Debug # print(fwenv.Dump()) diff --git a/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index cbb1e15a3..2873ea6b0 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.c +++ b/lib/lfrfid/protocols/lfrfid_protocols.c @@ -4,6 +4,7 @@ #include "protocol_h10301.h" #include "protocol_idteck.h" #include "protocol_indala26.h" +#include "protocol_indala224.h" #include "protocol_io_prox_xsf.h" #include "protocol_awid.h" #include "protocol_fdx_a.h" @@ -31,6 +32,7 @@ const ProtocolBase* const lfrfid_protocols[] = { [LFRFIDProtocolH10301] = &protocol_h10301, [LFRFIDProtocolIdteck] = &protocol_idteck, [LFRFIDProtocolIndala26] = &protocol_indala26, + [LFRFIDProtocolIndala224] = &protocol_indala224, [LFRFIDProtocolIOProxXSF] = &protocol_io_prox_xsf, [LFRFIDProtocolAwid] = &protocol_awid, [LFRFIDProtocolFDXA] = &protocol_fdx_a, diff --git a/lib/lfrfid/protocols/lfrfid_protocols.h b/lib/lfrfid/protocols/lfrfid_protocols.h index d6e13c2e4..14a6bdad2 100644 --- a/lib/lfrfid/protocols/lfrfid_protocols.h +++ b/lib/lfrfid/protocols/lfrfid_protocols.h @@ -16,6 +16,7 @@ typedef enum { LFRFIDProtocolH10301, LFRFIDProtocolIdteck, LFRFIDProtocolIndala26, + LFRFIDProtocolIndala224, LFRFIDProtocolIOProxXSF, LFRFIDProtocolAwid, LFRFIDProtocolFDXA, diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index afec0672a..6c13233df 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -284,9 +284,12 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { bool block_status = bit_lib_get_bit(protocol->data, 48); bool rudi_bit = bit_lib_get_bit(protocol->data, 49); - uint8_t reserved = bit_lib_get_bits(protocol->data, 50, 5); - uint8_t user_info = bit_lib_get_bits(protocol->data, 55, 5); - uint8_t replacement_number = bit_lib_get_bits(protocol->data, 60, 3); + uint8_t visual_start_digit = + bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 50, 3) << 5); + uint8_t reserved = bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 53, 2) << 6); + uint8_t user_info = bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 55, 5) << 3); + uint8_t replacement_number = + bit_lib_reverse_8_fast(bit_lib_get_bits(protocol->data, 60, 3) << 5); bool animal_flag = bit_lib_get_bit(protocol->data, 63); Storage* storage = furi_record_open(RECORD_STORAGE); FuriString* country_full_name = furi_string_alloc(); @@ -320,13 +323,19 @@ void protocol_fdx_b_render_data(ProtocolFDXB* protocol, FuriString* result) { result, "\n" "Animal: %s\n" - "Bits: %hhX-%hhX-%hhX-%hhX-%hhX", + "Visual Start Digit: %hu\n" + "Replacement Number: %hu\n" + "User Info: %hhX\n" + "Data Block: %s\n" + "RUDI Bit: %s\n" + "RFU: %hhX\n", animal_flag ? "Yes" : "No", - block_status, - rudi_bit, - reserved, + visual_start_digit, + replacement_number, user_info, - replacement_number); + block_status ? "Present" : "Absent", + rudi_bit ? "Yes" : "No", + reserved); furi_string_free(country_full_name); } diff --git a/lib/lfrfid/protocols/protocol_indala224.c b/lib/lfrfid/protocols/protocol_indala224.c new file mode 100644 index 000000000..553509669 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala224.c @@ -0,0 +1,281 @@ +#include +#include +#include +#include "lfrfid_protocols.h" + +#define INDALA224_PREAMBLE_BIT_SIZE (30) +#define INDALA224_PREAMBLE_DATA_SIZE (4) + +#define INDALA224_ENCODED_BIT_SIZE (224) +#define INDALA224_ENCODED_DATA_SIZE \ + (((INDALA224_ENCODED_BIT_SIZE) / 8) + INDALA224_PREAMBLE_DATA_SIZE) +#define INDALA224_ENCODED_DATA_LAST ((INDALA224_ENCODED_BIT_SIZE) / 8) + +#define INDALA224_DECODED_BIT_SIZE (224) +#define INDALA224_DECODED_DATA_SIZE (28) + +#define INDALA224_US_PER_BIT (255) +#define INDALA224_ENCODER_PULSES_PER_BIT (16) + +typedef struct { + uint8_t data_index; + uint8_t bit_clock_index; + bool current_polarity; + bool pulse_phase; +} ProtocolIndala224Encoder; + +typedef struct { + uint8_t encoded_data[INDALA224_ENCODED_DATA_SIZE]; + uint8_t negative_encoded_data[INDALA224_ENCODED_DATA_SIZE]; + + uint8_t data[INDALA224_DECODED_DATA_SIZE]; + ProtocolIndala224Encoder encoder; +} ProtocolIndala224; + +ProtocolIndala224* protocol_indala224_alloc(void) { + ProtocolIndala224* protocol = malloc(sizeof(ProtocolIndala224)); + return protocol; +} + +void protocol_indala224_free(ProtocolIndala224* protocol) { + free(protocol); +} + +uint8_t* protocol_indala224_get_data(ProtocolIndala224* protocol) { + return protocol->data; +} + +void protocol_indala224_decoder_start(ProtocolIndala224* protocol) { + memset(protocol->encoded_data, 0, INDALA224_ENCODED_DATA_SIZE); + memset(protocol->negative_encoded_data, 0, INDALA224_ENCODED_DATA_SIZE); +} + +static bool protocol_indala224_check_preamble(uint8_t* data, size_t bit_index) { + // Normal preamble: 1 followed by 29 zeros + if(bit_lib_get_bits(data, bit_index, 8) != 0b10000000) return false; + if(bit_lib_get_bits(data, bit_index + 8, 8) != 0) return false; + if(bit_lib_get_bits(data, bit_index + 16, 8) != 0) return false; + if(bit_lib_get_bits(data, bit_index + 24, 6) != 0) return false; + return true; +} + +static bool protocol_indala224_check_inverted_preamble(uint8_t* data, size_t bit_index) { + // Inverted preamble: 0 followed by 29 ones (phase-alternating PSK2 cards) + if(bit_lib_get_bits(data, bit_index, 8) != 0b01111111) return false; + if(bit_lib_get_bits(data, bit_index + 8, 8) != 0xFF) return false; + if(bit_lib_get_bits(data, bit_index + 16, 8) != 0xFF) return false; + if(bit_lib_get_bits(data, bit_index + 24, 6) != 0b111111) return false; + return true; +} + +static bool protocol_indala224_can_be_decoded(uint8_t* data) { + if(!protocol_indala224_check_preamble(data, 0)) return false; + // Second frame may have same or inverted preamble (PSK2 phase alternation) + if(!protocol_indala224_check_preamble(data, INDALA224_ENCODED_BIT_SIZE) && + !protocol_indala224_check_inverted_preamble(data, INDALA224_ENCODED_BIT_SIZE)) { + return false; + } + return true; +} + +static bool protocol_indala224_decoder_feed_internal(bool polarity, uint32_t time, uint8_t* data) { + time += (INDALA224_US_PER_BIT / 2); + + size_t bit_count = (time / INDALA224_US_PER_BIT); + bool result = false; + + if(bit_count < INDALA224_ENCODED_BIT_SIZE) { + for(size_t i = 0; i < bit_count; i++) { + bit_lib_push_bit(data, INDALA224_ENCODED_DATA_SIZE, polarity); + if(protocol_indala224_can_be_decoded(data)) { + result = true; + break; + } + } + } + + return result; +} + +static void protocol_indala224_decoder_save(uint8_t* data_to, const uint8_t* data_from) { + // PSK2 differential decode: the shift register contains phase values, not data. + // T5577 PSK2 encodes data as phase transitions: data[n] = phase[n] XOR phase[n+1]. + // Bit 224 (start of second frame) provides phase[n+1] for the last data bit. + for(size_t i = 0; i < INDALA224_DECODED_BIT_SIZE; i++) { + bool phase_current = bit_lib_get_bit(data_from, i); + bool phase_next = bit_lib_get_bit(data_from, i + 1); + bit_lib_set_bit(data_to, i, phase_current ^ phase_next); + } +} + +bool protocol_indala224_decoder_feed(ProtocolIndala224* protocol, bool level, uint32_t duration) { + bool result = false; + + if(duration > (INDALA224_US_PER_BIT / 2)) { + if(protocol_indala224_decoder_feed_internal(level, duration, protocol->encoded_data)) { + protocol_indala224_decoder_save(protocol->data, protocol->encoded_data); + FURI_LOG_D("Indala224", "Positive"); + result = true; + return result; + } + + if(protocol_indala224_decoder_feed_internal( + !level, duration, protocol->negative_encoded_data)) { + protocol_indala224_decoder_save(protocol->data, protocol->negative_encoded_data); + FURI_LOG_D("Indala224", "Negative"); + result = true; + return result; + } + } + + return result; +} + +bool protocol_indala224_encoder_start(ProtocolIndala224* protocol) { + // Store raw data for PSK2 emulation - the yield function handles PSK2 modulation. + memcpy(protocol->encoded_data, protocol->data, INDALA224_DECODED_DATA_SIZE); + + protocol->encoder.data_index = 0; + protocol->encoder.current_polarity = true; + protocol->encoder.pulse_phase = true; + protocol->encoder.bit_clock_index = 0; + + return true; +} + +LevelDuration protocol_indala224_encoder_yield(ProtocolIndala224* protocol) { + LevelDuration level_duration; + ProtocolIndala224Encoder* encoder = &protocol->encoder; + + if(encoder->pulse_phase) { + level_duration = level_duration_make(encoder->current_polarity, 1); + encoder->pulse_phase = false; + } else { + level_duration = level_duration_make(!encoder->current_polarity, 1); + encoder->pulse_phase = true; + + encoder->bit_clock_index++; + if(encoder->bit_clock_index >= INDALA224_ENCODER_PULSES_PER_BIT) { + encoder->bit_clock_index = 0; + + // PSK2: carrier phase flips when data bit is 1 + bool current_bit = bit_lib_get_bit(protocol->encoded_data, encoder->data_index); + if(current_bit) { + encoder->current_polarity = !encoder->current_polarity; + } + + bit_lib_increment_index(encoder->data_index, INDALA224_ENCODED_BIT_SIZE); + } + } + + return level_duration; +} + +static void protocol_indala224_render_data_internal( + ProtocolIndala224* protocol, + FuriString* result, + bool brief) { + if(brief) { + furi_string_printf( + result, + "Raw: %02X%02X%02X%02X\n" + " %02X%02X%02X%02X...", + protocol->data[0], + protocol->data[1], + protocol->data[2], + protocol->data[3], + protocol->data[4], + protocol->data[5], + protocol->data[6], + protocol->data[7]); + } else { + furi_string_printf( + result, + "Raw: %02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X %02X%02X%02X%02X\n" + "%02X%02X%02X%02X", + protocol->data[0], + protocol->data[1], + protocol->data[2], + protocol->data[3], + protocol->data[4], + protocol->data[5], + protocol->data[6], + protocol->data[7], + protocol->data[8], + protocol->data[9], + protocol->data[10], + protocol->data[11], + protocol->data[12], + protocol->data[13], + protocol->data[14], + protocol->data[15], + protocol->data[16], + protocol->data[17], + protocol->data[18], + protocol->data[19], + protocol->data[20], + protocol->data[21], + protocol->data[22], + protocol->data[23], + protocol->data[24], + protocol->data[25], + protocol->data[26], + protocol->data[27]); + } +} + +void protocol_indala224_render_data(ProtocolIndala224* protocol, FuriString* result) { + protocol_indala224_render_data_internal(protocol, result, false); +} + +void protocol_indala224_render_brief_data(ProtocolIndala224* protocol, FuriString* result) { + protocol_indala224_render_data_internal(protocol, result, true); +} + +bool protocol_indala224_write_data(ProtocolIndala224* protocol, void* data) { + LFRFIDWriteRequest* request = (LFRFIDWriteRequest*)data; + bool result = false; + + if(request->write_type == LFRFIDWriteTypeT5577) { + // Config: PSK2, RF/32, 7 data blocks (matches Proxmark3 T55X7_INDALA_224_CONFIG_BLOCK) + // T5577 data blocks contain raw data bits; the chip handles PSK2 modulation. + request->t5577.block[0] = LFRFID_T5577_BITRATE_RF_32 | LFRFID_T5577_MODULATION_PSK2 | + (7 << LFRFID_T5577_MAXBLOCK_SHIFT); + request->t5577.block[1] = bit_lib_get_bits_32(protocol->data, 0, 32); + request->t5577.block[2] = bit_lib_get_bits_32(protocol->data, 32, 32); + request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32); + request->t5577.block[4] = bit_lib_get_bits_32(protocol->data, 96, 32); + request->t5577.block[5] = bit_lib_get_bits_32(protocol->data, 128, 32); + request->t5577.block[6] = bit_lib_get_bits_32(protocol->data, 160, 32); + request->t5577.block[7] = bit_lib_get_bits_32(protocol->data, 192, 32); + request->t5577.blocks_to_write = 8; + result = true; + } + return result; +} + +const ProtocolBase protocol_indala224 = { + .name = "Indala224", + .manufacturer = "Motorola", + .data_size = INDALA224_DECODED_DATA_SIZE, + .features = LFRFIDFeaturePSK, + .validate_count = 6, + .alloc = (ProtocolAlloc)protocol_indala224_alloc, + .free = (ProtocolFree)protocol_indala224_free, + .get_data = (ProtocolGetData)protocol_indala224_get_data, + .decoder = + { + .start = (ProtocolDecoderStart)protocol_indala224_decoder_start, + .feed = (ProtocolDecoderFeed)protocol_indala224_decoder_feed, + }, + .encoder = + { + .start = (ProtocolEncoderStart)protocol_indala224_encoder_start, + .yield = (ProtocolEncoderYield)protocol_indala224_encoder_yield, + }, + .render_data = (ProtocolRenderData)protocol_indala224_render_data, + .render_brief_data = (ProtocolRenderData)protocol_indala224_render_brief_data, + .write_data = (ProtocolWriteData)protocol_indala224_write_data, +}; diff --git a/lib/lfrfid/protocols/protocol_indala224.h b/lib/lfrfid/protocols/protocol_indala224.h new file mode 100644 index 000000000..de23d8034 --- /dev/null +++ b/lib/lfrfid/protocols/protocol_indala224.h @@ -0,0 +1,4 @@ +#pragma once +#include + +extern const ProtocolBase protocol_indala224; diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 4a92956a9..86bb25197 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -172,7 +172,10 @@ bool iso14443_4_layer_decode_response( bit_buffer_copy_right(output_data, block_data, 1); } else { if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; - bit_buffer_copy_right(output_data, block_data, 1); + // Fix for some EMV cards with strange response + if(bit_buffer_get_size_bytes(block_data) > 1) { + bit_buffer_copy_right(output_data, block_data, 1); + } ret = true; } } while(false); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3.h b/lib/nfc/protocols/iso15693_3/iso15693_3.h index fdff7112b..d727429bd 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3.h @@ -15,6 +15,11 @@ extern "C" { #define ISO15693_3_FDT_LISTEN_FC (4320U) #define ISO15693_3_POLL_POLL_MIN_US (1500U) +/* NVM write time varies by tag type (ICODE SLIX: 9.6ms, generic: up to 20ms). + * ISO 15693-3 write commands require a longer FWT than reads to account for + * EEPROM programming delays before the tag responds. 20ms covers all known tags. */ +#define ISO15693_3_FDT_WRITE_POLL_FC (271200U) + #define ISO15693_3_REQ_FLAG_SUBCARRIER_1 (0U << 0) #define ISO15693_3_REQ_FLAG_SUBCARRIER_2 (1U << 0) #define ISO15693_3_REQ_FLAG_DATA_RATE_LO (0U << 1) diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c index 3d8d95c3a..b3f68e0b5 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.c @@ -176,6 +176,25 @@ Iso15693_3Error return ret; } +Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf) { + Iso15693_3Error ret = Iso15693_3ErrorNone; + + do { + if(iso15693_3_error_response_parse(&ret, buf)) break; + + typedef struct { + uint8_t flags; + } WriteBlockResponseLayout; + + if(bit_buffer_get_size_bytes(buf) != sizeof(WriteBlockResponseLayout)) { + ret = Iso15693_3ErrorUnexpectedResponse; + break; + } + } while(false); + + return ret; +} + Iso15693_3Error iso15693_3_get_block_security_response_parse( uint8_t* data, uint16_t block_count, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h index 253bda7f5..955b67409 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_i.h @@ -28,6 +28,8 @@ Iso15693_3Error Iso15693_3Error iso15693_3_read_block_response_parse(uint8_t* data, uint8_t block_size, const BitBuffer* buf); +Iso15693_3Error iso15693_3_write_block_response_parse(const BitBuffer* buf); + Iso15693_3Error iso15693_3_get_block_security_response_parse( uint8_t* data, uint16_t block_count, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h index efe8337af..0f52012e9 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller.h @@ -13,6 +13,14 @@ extern "C" { */ typedef struct Iso15693_3Poller Iso15693_3Poller; +/** + * @brief Get the Iso15693_3 data accumulated by the poller. + * + * @param[in] instance pointer to the Iso15693_3Poller instance. + * @return pointer to the Iso15693_3Data structure filled during activation. + */ +const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); + /** * @brief Enumeration of possible Iso15693_3 poller event types. */ @@ -144,6 +152,40 @@ Iso15693_3Error iso15693_3_poller_get_blocks_security( uint8_t* data, uint16_t block_count); +/** + * @brief Write a single Iso15693_3 block. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] data pointer to the buffer containing the data to write. + * @param[in] block_number block number to write. + * @param[in] block_size size of the block in bytes. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_write_block( + Iso15693_3Poller* instance, + const uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +/** + * @brief Write multiple consecutive Iso15693_3 blocks. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] data pointer to the buffer containing the data to write. + * @param[in] block_count number of blocks to write. + * @param[in] block_size size of each block in bytes. + * @return Iso15693_3ErrorNone on success, an error code on failure. + */ +Iso15693_3Error iso15693_3_poller_write_blocks( + Iso15693_3Poller* instance, + const uint8_t* data, + uint16_t block_count, + uint8_t block_size); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c index bc677ce67..f738f2087 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -234,6 +234,57 @@ Iso15693_3Error iso15693_3_poller_read_blocks( return ret; } +Iso15693_3Error iso15693_3_poller_write_block( + Iso15693_3Poller* instance, + const uint8_t* data, + uint8_t block_number, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + bit_buffer_append_byte( + instance->tx_buffer, ISO15693_3_REQ_FLAG_SUBCARRIER_1 | ISO15693_3_REQ_FLAG_DATA_RATE_HI); + bit_buffer_append_byte(instance->tx_buffer, ISO15693_3_CMD_WRITE_BLOCK); + bit_buffer_append_byte(instance->tx_buffer, block_number); + bit_buffer_append_bytes(instance->tx_buffer, data, block_size); + + Iso15693_3Error ret; + + do { + ret = iso15693_3_poller_send_frame( + instance, instance->tx_buffer, instance->rx_buffer, ISO15693_3_FDT_WRITE_POLL_FC); + if(ret != Iso15693_3ErrorNone) break; + + ret = iso15693_3_write_block_response_parse(instance->rx_buffer); + } while(false); + + return ret; +} + +Iso15693_3Error iso15693_3_poller_write_blocks( + Iso15693_3Poller* instance, + const uint8_t* data, + uint16_t block_count, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + furi_assert(block_count); + furi_assert(block_size); + + Iso15693_3Error ret = Iso15693_3ErrorNone; + + for(uint32_t i = 0; i < block_count; ++i) { + ret = + iso15693_3_poller_write_block(instance, &data[block_size * i], (uint8_t)i, block_size); + if(ret != Iso15693_3ErrorNone) break; + } + + return ret; +} + Iso15693_3Error iso15693_3_poller_get_blocks_security( Iso15693_3Poller* instance, uint8_t* data, diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h index 346d0d724..1a0f48139 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.h @@ -33,8 +33,6 @@ struct Iso15693_3Poller { void* context; }; -const Iso15693_3Data* iso15693_3_poller_get_data(Iso15693_3Poller* instance); - #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller.c b/lib/nfc/protocols/mf_classic/mf_classic_poller.c index ae2f5467f..86a3ccddd 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller.c +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller.c @@ -65,24 +65,28 @@ void mf_classic_poller_free(MfClassicPoller* instance) { bit_buffer_free(instance->tx_encrypted_buffer); bit_buffer_free(instance->rx_encrypted_buffer); - // Clean up resources in MfClassicPollerDictAttackContext - MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; + // Clean up dict attack resources when the poller was in dict attack mode. + if(instance->mode == MfClassicPollerModeDictAttackStandard || + instance->mode == MfClassicPollerModeDictAttackEnhanced || + instance->mode == MfClassicPollerModeDictAttackCUID) { + MfClassicPollerDictAttackContext* dict_attack_ctx = &instance->mode_ctx.dict_attack_ctx; - // Free the dictionaries - if(dict_attack_ctx->mf_classic_system_dict) { - keys_dict_free(dict_attack_ctx->mf_classic_system_dict); - dict_attack_ctx->mf_classic_system_dict = NULL; - } - if(dict_attack_ctx->mf_classic_user_dict) { - keys_dict_free(dict_attack_ctx->mf_classic_user_dict); - dict_attack_ctx->mf_classic_user_dict = NULL; - } + // Free the dictionaries + if(dict_attack_ctx->mf_classic_system_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_system_dict); + dict_attack_ctx->mf_classic_system_dict = NULL; + } + if(dict_attack_ctx->mf_classic_user_dict) { + keys_dict_free(dict_attack_ctx->mf_classic_user_dict); + dict_attack_ctx->mf_classic_user_dict = NULL; + } - // Free the nested nonce array if it exists - if(dict_attack_ctx->nested_nonce.nonces) { - free(dict_attack_ctx->nested_nonce.nonces); - dict_attack_ctx->nested_nonce.nonces = NULL; - dict_attack_ctx->nested_nonce.count = 0; + // Free the nested nonce array if it exists + if(dict_attack_ctx->nested_nonce.nonces) { + free(dict_attack_ctx->nested_nonce.nonces); + dict_attack_ctx->nested_nonce.nonces = NULL; + dict_attack_ctx->nested_nonce.count = 0; + } } free(instance); @@ -162,6 +166,7 @@ NfcCommand mf_classic_poller_handler_start(MfClassicPoller* instance) { instance->mfc_event.type = MfClassicPollerEventTypeRequestMode; command = instance->callback(instance->general_event, instance->context); + instance->mode = instance->mfc_event_data.poller_mode.mode; if(instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackStandard || instance->mfc_event_data.poller_mode.mode == MfClassicPollerModeDictAttackCUID) { diff --git a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h index 607b126a0..3043deb15 100644 --- a/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h +++ b/lib/nfc/protocols/mf_classic/mf_classic_poller_i.h @@ -183,6 +183,7 @@ struct MfClassicPoller { MfClassicType current_type_check; uint8_t sectors_total; + MfClassicPollerMode mode; MfClassicPollerModeContext mode_ctx; Crypto1* crypto; diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c index b90ef8ccf..886049685 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_i.c @@ -290,9 +290,7 @@ bool mf_desfire_file_settings_parse(MfDesfireFileSettings* data, const BitBuffer printf("\r\n"); break; } - if(additional_access_rights_len > - MF_DESFIRE_MAX_KEYS * sizeof(MfDesfireFileAccessRights)) - break; + if(additional_access_rights_len > MF_DESFIRE_MAX_KEYS - 1) break; memcpy( &file_settings_temp.access_rights[1], diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c index d1f08294f..8abfb05d2 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight.c @@ -575,13 +575,15 @@ uint8_t mf_ultralight_get_write_end_page(MfUltralightType type) { furi_assert( type == MfUltralightTypeUL11 || type == MfUltralightTypeUL21 || type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || - type == MfUltralightTypeNTAG216 || type == MfUltralightTypeOrigin); + type == MfUltralightTypeNTAG216 || type == MfUltralightTypeOrigin || + type == MfUltralightTypeMfulC); uint8_t end_page = mf_ultralight_get_config_page_num(type); if(type == MfUltralightTypeNTAG213 || type == MfUltralightTypeNTAG215 || type == MfUltralightTypeNTAG216) { end_page -= 1; - } else if(type == MfUltralightTypeOrigin) { + } else if(type == MfUltralightTypeOrigin || type == MfUltralightTypeMfulC) { + // ULC: 48 pages total, write pages 4-47 (includes auth config + 3DES key) end_page = mf_ultralight_features[type].total_pages; } diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c index 050f9abc1..9a25d81b6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -169,6 +169,10 @@ static MfUltralightCommand MfUltralightPage pages[64] = {}; uint8_t page_cnt = (end_page - start_page) + 1; + if(page_cnt > COUNT_OF(pages)) { + command = MfUltralightCommandNotProcessedNAK; + break; + } mf_ultralight_listener_perform_read(pages, instance, start_page, page_cnt, do_i2c_check); bit_buffer_copy_bytes(instance->tx_buffer, (uint8_t*)pages, page_cnt * 4); @@ -186,7 +190,7 @@ static MfUltralightCommand uint16_t pages_total = instance->data->pages_total; MfUltralightCommand command = MfUltralightCommandNotProcessedNAK; - FURI_LOG_T(TAG, "CMD_WRITE"); + FURI_LOG_T(TAG, "CMD_WRITE page %d", start_page); do { bool do_i2c_check = mf_ultralight_is_i2c_tag(instance->data->type); @@ -197,12 +201,22 @@ static MfUltralightCommand break; } - if(!mf_ultralight_listener_check_access( - instance, start_page, MfUltralightListenerAccessTypeWrite)) - break; + // PATCHED: For Ultralight-C, allow writes to pages 44-47 (3DES key area) + // This enables "magic card" emulation for key grabbing + bool is_ulc_key_page = (instance->data->type == MfUltralightTypeMfulC) && + (start_page >= 44 && start_page <= 47); - if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; - if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + if(!is_ulc_key_page) { + // Normal access check for all other pages + if(!mf_ultralight_listener_check_access( + instance, start_page, MfUltralightListenerAccessTypeWrite)) + break; + + if(mf_ultralight_static_lock_check_page(instance->static_lock, start_page)) break; + if(mf_ultralight_dynamic_lock_check_page(instance, start_page)) break; + } else { + FURI_LOG_I(TAG, "MAGIC: Allowing write to ULC key page %d", start_page); + } const uint8_t* rx_data = bit_buffer_get_data(buffer); command = diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c index 80518b1a8..6dd761af8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -698,9 +698,12 @@ static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPo instance->mfu_event.type = MfUltralightPollerEventTypeRequestWriteData; instance->callback(instance->general_event, instance->context); - const MfUltralightData* write_data = instance->mfu_event.data->write_data; + // Save write_data to instance field before any further events clobber the union + instance->write_data = instance->mfu_event.data->write_data; + const MfUltralightData* write_data = instance->write_data; const MfUltralightData* tag_data = instance->data; uint32_t features = mf_ultralight_get_feature_support_set(tag_data->type); + instance->write_skip_key = false; bool check_passed = false; do { @@ -738,6 +741,71 @@ static NfcCommand mf_ultralight_poller_handler_request_write_data(MfUltralightPo check_passed = true; } while(false); + // ULC: authenticate the target card before writing. + // The read phase left the card unauthenticated (write-mode callback skips read-phase auth). + // Ask callback for keys (cache first, then dict) until one authenticates, then fire + // WriteKeyRequest so the callback can cache the found key and return the skip decision. + if(check_passed && + mf_ultralight_support_feature(features, MfUltralightFeatureSupportAuthenticate)) { + bool auth_ok = false; + + while(!auth_ok) { + // Request next key from callback (tries dict entries) + memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData)); + instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; + instance->callback(instance->general_event, instance->context); + + if(!instance->mfu_event.data->key_request_data.key_provided) { + FURI_LOG_D(TAG, "ULC write: all keys exhausted"); + break; + } + instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; + + // Halt+activate so the card is in a clean state for auth + iso14443_3a_poller_halt(instance->iso14443_3a_poller); + if(iso14443_3a_poller_activate(instance->iso14443_3a_poller, NULL) != + Iso14443_3aErrorNone) { + FURI_LOG_E(TAG, "ULC write: card not responding (locked out?)"); + break; + } + + uint8_t output[MF_ULTRALIGHT_C_AUTH_DATA_SIZE]; + uint8_t RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + furi_hal_random_fill_buf(RndA, sizeof(RndA)); + if(mf_ultralight_poller_authenticate_start(instance, RndA, output) != + MfUltralightErrorNone) { + break; + } + uint8_t decoded_RndA[MF_ULTRALIGHT_C_AUTH_RND_BLOCK_SIZE] = {0}; + const uint8_t* RndB = output + MF_ULTRALIGHT_C_AUTH_RND_B_BLOCK_OFFSET; + if(mf_ultralight_poller_authenticate_end(instance, RndB, output, decoded_RndA) != + MfUltralightErrorNone) + continue; + mf_ultralight_3des_shift_data(RndA); + auth_ok = (memcmp(RndA, decoded_RndA, sizeof(decoded_RndA)) == 0); + FURI_LOG_D(TAG, "ULC write auth attempt: %s", auth_ok ? "success" : "fail"); + } + + if(auth_ok) { + // Notify callback with the found key: it caches it and returns the skip decision + MfUltralightC3DesAuthKey found_key = instance->auth_context.tdes_key; + memset(instance->mfu_event.data, 0, sizeof(MfUltralightPollerEventData)); + instance->mfu_event.data->key_request_data.key = found_key; + instance->mfu_event.data->key_request_data.key_provided = true; + instance->mfu_event.type = MfUltralightPollerEventTypeWriteKeyRequest; + instance->callback(instance->general_event, instance->context); + instance->write_skip_key = instance->mfu_event.data->write_key_skip; + FURI_LOG_D( + TAG, + "ULC write: key %s", + instance->write_skip_key ? "kept (target unchanged)" : "overwrite with source"); + } else { + FURI_LOG_E(TAG, "ULC write auth failed - card locked"); + check_passed = false; + instance->mfu_event.type = MfUltralightPollerEventTypeCardLocked; + } + } + if(!check_passed) { iso14443_3a_poller_halt(instance->iso14443_3a_poller); command = instance->callback(instance->general_event, instance->context); @@ -752,15 +820,52 @@ static NfcCommand mf_ultralight_poller_handler_write_pages(MfUltralightPoller* i NfcCommand command = NfcCommandContinue; do { - const MfUltralightData* write_data = instance->mfu_event.data->write_data; + // Use the saved write_data pointer - the union was overwritten by WriteKeyRequest + const MfUltralightData* write_data = instance->write_data; uint8_t end_page = mf_ultralight_get_write_end_page(write_data->type); + + // If user chose to keep target's key, stop before the ULC key pages (44-47) + if(instance->write_skip_key && write_data->type == MfUltralightTypeMfulC && + end_page > 44) { + end_page = 44; + } + if(instance->current_page == end_page) { instance->state = MfUltralightPollerStateWriteSuccess; break; } - FURI_LOG_D(TAG, "Writing page %d", instance->current_page); - MfUltralightError error = mf_ultralight_poller_write_page( - instance, instance->current_page, &write_data->page[instance->current_page]); + + // For ULC key pages (44-47): byte-order correction required. + // Flipper stores each 8-byte DES sub-key MSB-first; the card expects each half reversed. + // Transform: card_bytes = reverse(flipper[0..7]) || reverse(flipper[8..15]) + MfUltralightPage page_to_write; + if(instance->current_page >= 44 && instance->current_page <= 47 && + write_data->type == MfUltralightTypeMfulC) { + const uint8_t* raw = (const uint8_t*)&write_data->page[44]; + uint8_t xformed[16]; + for(int i = 0; i < 8; i++) { + xformed[i] = raw[7 - i]; + } + for(int i = 0; i < 8; i++) { + xformed[8 + i] = raw[15 - i]; + } + uint8_t page_offset = (instance->current_page - 44) * 4; + memcpy(page_to_write.data, xformed + page_offset, 4); + FURI_LOG_D( + TAG, + "Writing KEY page %d (byte-order corrected): %02X %02X %02X %02X", + instance->current_page, + page_to_write.data[0], + page_to_write.data[1], + page_to_write.data[2], + page_to_write.data[3]); + } else { + page_to_write = write_data->page[instance->current_page]; + FURI_LOG_D(TAG, "Writing page %d", instance->current_page); + } + + MfUltralightError error = + mf_ultralight_poller_write_page(instance, instance->current_page, &page_to_write); if(error != MfUltralightErrorNone) { instance->state = MfUltralightPollerStateWriteFail; instance->error = error; diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h index 2552abeb5..c0a38cfe9 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h @@ -28,6 +28,7 @@ typedef enum { MfUltralightPollerEventTypeWriteSuccess, /**< Poller wrote card successfully. */ MfUltralightPollerEventTypeWriteFail, /**< Poller failed to write card. */ MfUltralightPollerEventTypeRequestKey, /**< Poller requests key for dict attack. */ + MfUltralightPollerEventTypeWriteKeyRequest, /**< Poller asks user whether to overwrite 3DES key on target. */ } MfUltralightPollerEventType; /** @@ -67,6 +68,7 @@ typedef union { const MfUltralightData* write_data; /**< Data to be written to card. */ MfUltralightPollerMode poller_mode; /**< Mode to operate in. */ MfUltralightPollerKeyRequestData key_request_data; /**< Key request data. */ + bool write_key_skip; /**< Set to true by callback to skip writing 3DES key pages. */ } MfUltralightPollerEventData; /** diff --git a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h index b35c49aea..a6d9235b0 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller_i.h @@ -88,6 +88,8 @@ struct MfUltralightPoller { uint8_t tearing_flag_read; uint8_t tearing_flag_total; uint16_t current_page; + bool write_skip_key; // If true, skip writing pages 44-47 (3DES key) during ULC write + const MfUltralightData* write_data; // Saved pointer to source data for write phase MfUltralightError error; mbedtls_des3_context des_context; diff --git a/lib/nfc/protocols/slix/slix_poller.c b/lib/nfc/protocols/slix/slix_poller.c index aeb1180cf..f69f58fa1 100644 --- a/lib/nfc/protocols/slix/slix_poller.c +++ b/lib/nfc/protocols/slix/slix_poller.c @@ -54,7 +54,7 @@ static NfcCommand slix_poller_handler_idle(SlixPoller* instance) { return NfcCommandContinue; } -static NfcCommand slix_poller_handler_get_nfc_system_info(SlixPoller* instance) { +static NfcCommand slix_poller_handler_get_nxp_sys_info(SlixPoller* instance) { if(slix_type_has_features(instance->type, SLIX_TYPE_FEATURE_NFC_SYSTEM_INFO)) { instance->error = slix_poller_get_nxp_system_info(instance, &instance->data->system_info); if(instance->error == SlixErrorNone) { @@ -186,7 +186,7 @@ static NfcCommand slix_poller_handler_ready(SlixPoller* instance) { static const SlixPollerStateHandler slix_poller_state_handler[SlixPollerStateNum] = { [SlixPollerStateIdle] = slix_poller_handler_idle, [SlixPollerStateError] = slix_poller_handler_error, - [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nfc_system_info, + [SlixPollerStateGetNxpSysInfo] = slix_poller_handler_get_nxp_sys_info, [SlixPollerStateReadSignature] = slix_poller_handler_read_signature, [SlixPollerStateCheckPrivacyPassword] = slix_poller_handler_check_privacy_password, [SlixPollerStatePrivacyUnlock] = slix_poller_handler_privacy_unlock, diff --git a/lib/nfc/protocols/slix/slix_poller.h b/lib/nfc/protocols/slix/slix_poller.h index e78f7882a..e0fee1a6a 100644 --- a/lib/nfc/protocols/slix/slix_poller.h +++ b/lib/nfc/protocols/slix/slix_poller.h @@ -118,6 +118,40 @@ SlixError slix_poller_set_password( SlixPassword password, SlixRandomNumber random_number); +/** + * @brief Write a single block to a Slix card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] data pointer to the buffer containing the data to write. + * @param[in] block_number block number to write. + * @param[in] block_size size of the block in bytes. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_write_block( + SlixPoller* instance, + const uint8_t* data, + uint8_t block_number, + uint8_t block_size); + +/** + * @brief Write multiple consecutive blocks to a Slix card. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[in] data pointer to the buffer containing the data to write. + * @param[in] block_count number of blocks to write. + * @param[in] block_size size of each block in bytes. + * @return SlixErrorNone on success, an error code on failure. + */ +SlixError slix_poller_write_blocks( + SlixPoller* instance, + const uint8_t* data, + uint16_t block_count, + uint8_t block_size); + #ifdef __cplusplus } #endif diff --git a/lib/nfc/protocols/slix/slix_poller_i.c b/lib/nfc/protocols/slix/slix_poller_i.c index ee6912cc4..7759e1591 100644 --- a/lib/nfc/protocols/slix/slix_poller_i.c +++ b/lib/nfc/protocols/slix/slix_poller_i.c @@ -128,3 +128,31 @@ SlixError slix_poller_set_password( return error; } + +SlixError slix_poller_write_block( + SlixPoller* instance, + const uint8_t* data, + uint8_t block_number, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + + const Iso15693_3Error error = + iso15693_3_poller_write_block(instance->iso15693_3_poller, data, block_number, block_size); + return slix_process_iso15693_3_error(error); +} + +SlixError slix_poller_write_blocks( + SlixPoller* instance, + const uint8_t* data, + uint16_t block_count, + uint8_t block_size) { + furi_assert(instance); + furi_assert(data); + furi_assert(block_count); + furi_assert(block_size); + + const Iso15693_3Error error = + iso15693_3_poller_write_blocks(instance->iso15693_3_poller, data, block_count, block_size); + return slix_process_iso15693_3_error(error); +} diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 607d0a80f..d44ac4492 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -17,7 +17,7 @@ void subghz_block_generic_global_counter_override_set(uint32_t counter) { bool subghz_block_generic_global_counter_override_get(uint32_t* counter) { // if override flag was enabled then return succes TRUE and return overrided counter, else return success = FALSE - // we cut counter bit length to available protocol bits length by the logical AND function + // we cut counter bits length to available protocol bits length by the logical AND function if(subghz_block_generic_global.cnt_need_override) { *counter = subghz_block_generic_global.new_cnt & ((0xFFFFFFFF >> (32 - subghz_block_generic_global.cnt_length_bit))); @@ -28,9 +28,30 @@ bool subghz_block_generic_global_counter_override_get(uint32_t* counter) { } } +void subghz_block_generic_global_button_override_set(uint8_t button) { + subghz_block_generic_global.new_btn = button; // set global variable + subghz_block_generic_global.btn_need_override = true; // set flag for protocols +} + +bool subghz_block_generic_global_button_override_get(uint8_t* button) { + // if override flag was enabled then return succes TRUE and return overrided button, else return success = FALSE + // we cut button bits length to available protocol bits length by the logical AND function + if(subghz_block_generic_global.btn_need_override) { + *button = subghz_block_generic_global.new_btn & + ((0xFF >> (8 - subghz_block_generic_global.btn_length_bit))); + subghz_block_generic_global.btn_need_override = false; + return true; + } else { + return false; + } +} + void subghz_block_generic_global_reset(void* p) { UNUSED(p); + // dont reset endless_tx, its used in protocols yield function to undless TX + bool tmp = subghz_block_generic_global.endless_tx; memset(&subghz_block_generic_global, 0, sizeof(subghz_block_generic_global)); + subghz_block_generic_global.endless_tx = tmp; } void subghz_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 1e72c5a5e..788467a79 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -30,28 +30,49 @@ struct SubGhzBlockGeneric { typedef struct SubGhzBlockGenericGlobal SubGhzBlockGenericGlobal; struct SubGhzBlockGenericGlobal { - uint32_t current_cnt; // global counter value; - uint32_t new_cnt; // global counter value; + uint32_t current_cnt; // current counter value; + uint32_t new_cnt; // new counter value; bool cnt_need_override; // flag for protocols to override signals counter inside of protocols - uint8_t cnt_length_bit; // counter length in bytes (used in counter editor giu) + uint8_t cnt_length_bit; // counter length in bits (used in counter editor giu) bool cnt_is_available; // is there counter available for protocol (used in counter editor giu) + + uint8_t current_btn; // current button value; + uint8_t new_btn; // new button value; + bool btn_need_override; // flag for protocols to override button inside of protocols + uint8_t btn_length_bit; // button length in bits (used in counter editor giu) + bool btn_is_available; // is there button available for protocol (used in button editor giu) + + bool endless_tx; // used for endless/breakless transmission in subghz protols yield function (when user hold OK button) }; extern SubGhzBlockGenericGlobal subghz_block_generic_global; //global structure for subghz /** - * Setup SubGhzBlockGenericGlobal.cnt and cnt_need_override flag to be used in protocols; + * Setup new_cnt and cnt_need_override flag to be used in protocols; * @param counter new counter value; */ void subghz_block_generic_global_counter_override_set(uint32_t counter); /** - * Return true if incomming variable was overrided by SubGhzBlockGenericGlobal.cnt + * Return true if incomming variable was overrided by new_cnt * else return false and not change incomming variable * @param counter pointer to counter variable that must be changed */ bool subghz_block_generic_global_counter_override_get(uint32_t* counter); +/** + * Setup new_btn and btn_need_override flag to be used in protocols; + * @param button new button value; + */ +void subghz_block_generic_global_button_override_set(uint8_t button); + +/** + * Return true if incomming variable was overrided by new_btn + * else return false and not change incomming variable + * @param button pointer to counter variable that must be changed + */ +bool subghz_block_generic_global_button_override_get(uint8_t* button); + /** * Reset subghz_block_generic global structure; */ diff --git a/lib/subghz/protocols/allstar_firefly.c b/lib/subghz/protocols/allstar_firefly.c new file mode 100644 index 000000000..de194b118 --- /dev/null +++ b/lib/subghz/protocols/allstar_firefly.c @@ -0,0 +1,378 @@ +/** + * allstar_firefly.c -- Allstar Firefly 318ALD31K native SubGHz protocol + * + * Implements the SubGhzProtocol vtable for the Supertex ED-9 based gate remote. + * Uses subghz_block_generic_serialize / deserialize for standard-format .sub + * files, encoding the 9-position trinary DIP code as a uint64 (base-3, MSB + * first: '+' = 2, '0' = 3, '-' = 0). + * + * Saved file format: + * Filetype: Flipper SubGhz Key File + * Version: 1 + * Frequency: 318000000 + * Preset: FuriHalSubGhzPresetOok650Async + * Protocol: Allstar Firefly + * Key: 00 00 00 00 00 00 34 B9 + * Bit: 18 + * + * See allstar_firefly.h for full protocol documentation. + */ + +#include "allstar_firefly.h" + +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "AllstarFirefly" + +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) + +#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c" + +#define SHOW_DIP_P(dip, check_dip) \ + ((((dip >> 0x10) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \ + ((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_') + +static const SubGhzBlockConst subghz_protocol_allstar_firefly_const = { + .te_short = 600, + .te_long = 4000, + .te_delta = 300, + .min_count_bit_for_found = 18, +}; + +struct SubGhzProtocolDecoderAllstarFirefly { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderAllstarFirefly { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + AllstarFireflyDecoderStepReset = 0, + AllstarFireflyDecoderStepSaveDuration, + AllstarFireflyDecoderStepCheckDuration, +} AllstarFireflyDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder = { + .alloc = subghz_protocol_decoder_allstar_firefly_alloc, + .free = subghz_protocol_decoder_allstar_firefly_free, + + .feed = subghz_protocol_decoder_allstar_firefly_feed, + .reset = subghz_protocol_decoder_allstar_firefly_reset, + + .get_hash_data = NULL, + .get_hash_data_long = subghz_protocol_decoder_allstar_firefly_get_hash_data, + .serialize = subghz_protocol_decoder_allstar_firefly_serialize, + .deserialize = subghz_protocol_decoder_allstar_firefly_deserialize, + .get_string = subghz_protocol_decoder_allstar_firefly_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder = { + .alloc = subghz_protocol_encoder_allstar_firefly_alloc, + .free = subghz_protocol_encoder_allstar_firefly_free, + + .deserialize = subghz_protocol_encoder_allstar_firefly_deserialize, + .stop = subghz_protocol_encoder_allstar_firefly_stop, + .yield = subghz_protocol_encoder_allstar_firefly_yield, +}; + +const SubGhzProtocol subghz_protocol_allstar_firefly = { + .name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_allstar_firefly_decoder, + .encoder = &subghz_protocol_allstar_firefly_encoder, +}; + +void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderAllstarFirefly* instance = + malloc(sizeof(SubGhzProtocolEncoderAllstarFirefly)); + + instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 5; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_allstar_firefly_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderAllstarFirefly* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderAllstarFirefly instance + */ +static void subghz_protocol_encoder_allstar_firefly_get_upload( + SubGhzProtocolEncoderAllstarFirefly* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_allstar_firefly_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_allstar_firefly_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = level_duration_make( + true, (uint32_t)subghz_protocol_allstar_firefly_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_allstar_firefly_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderAllstarFirefly* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_allstar_firefly_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_allstar_firefly_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_allstar_firefly_stop(void* context) { + SubGhzProtocolEncoderAllstarFirefly* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) { + SubGhzProtocolEncoderAllstarFirefly* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderAllstarFirefly* instance = + malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly)); + instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_allstar_firefly_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + free(instance); +} + +void subghz_protocol_decoder_allstar_firefly_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + instance->decoder.parser_step = AllstarFireflyDecoderStepReset; +} + +void subghz_protocol_decoder_allstar_firefly_feed( + void* context, + bool level, + volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + + // Allstar Firefly Decoder + // 2026 - by @jlaughter + // Remake to match other protocols code base - @xMasterX (MMX) + + switch(instance->decoder.parser_step) { + case AllstarFireflyDecoderStepReset: + if((!level) && + (DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) < + subghz_protocol_allstar_firefly_const.te_delta * 5)) { + // 30k us + // Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration; + } + break; + case AllstarFireflyDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = AllstarFireflyDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = AllstarFireflyDecoderStepReset; + } + break; + case AllstarFireflyDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 4000us HIGH (te_last) and 600us LOW + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) < + subghz_protocol_allstar_firefly_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short) < + subghz_protocol_allstar_firefly_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration; + // Bit 0 is short and long timing = 600us HIGH (te_last) and 4000us LOW + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_short) < + subghz_protocol_allstar_firefly_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_long) < + subghz_protocol_allstar_firefly_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) < + subghz_protocol_allstar_firefly_const.te_delta * 5) { + // Found next GAP and add bit 0 or 1 + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) < + subghz_protocol_allstar_firefly_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF( + instance->decoder.te_last, + subghz_protocol_allstar_firefly_const.te_short) < + subghz_protocol_allstar_firefly_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got 18 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_allstar_firefly_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = AllstarFireflyDecoderStepReset; + } else { + instance->decoder.parser_step = AllstarFireflyDecoderStepReset; + } + } else { + instance->decoder.parser_step = AllstarFireflyDecoderStepReset; + } + break; + } +} + +uint32_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_allstar_firefly_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%05lX Yek:0x%05lX\r\n" + " +: " DIP_PATTERN "\r\n" + " o: " DIP_PATTERN "\r\n" + " -: " DIP_PATTERN "\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFF), + (uint32_t)(code_found_reverse & 0xFFFFF), + SHOW_DIP_P(instance->generic.data, DIP_P), + SHOW_DIP_P(instance->generic.data, DIP_O), + SHOW_DIP_P(instance->generic.data, DIP_N)); +} diff --git a/lib/subghz/protocols/allstar_firefly.h b/lib/subghz/protocols/allstar_firefly.h new file mode 100644 index 000000000..b705635ec --- /dev/null +++ b/lib/subghz/protocols/allstar_firefly.h @@ -0,0 +1,143 @@ +#pragma once + +/** + * allstar_firefly.h - Allstar Firefly 318ALD31K native SubGHz protocol + * + * Registers the Allstar Firefly gate remote as a first-class SubGHz protocol, + * supporting decode from air, save/load of .sub files, and retransmit. + * + * Protocol summary (measured from captured remotes): + * Carrier : 318 MHz OOK (FuriHalSubGhzPresetOok650Async) + * Code type : Static 9-bit trinary (Supertex ED-9 encoder IC) + * Frame : 18 symbols (2 per bit), no separate sync pulse + * Repeats : 20 frames per keypress + * Inter-frame gap: ~30 440 us + * + * Symbol encoding - each bit = two (pulse, gap) pairs: + * '+' ON : H H = [4045us HIGH, 607us LOW] x2 + * '-' OFF : L L = [530us HIGH, 4139us LOW] x2 + * '0' FLOAT : H L = [4045us HIGH, 607us LOW, 530us HIGH, 4139us LOW] + * + * Save file format: + * Filetype: Flipper SubGhz Key File + * Version: 1 + * Frequency: 318000000 + * Preset: FuriHalSubGhzPresetOok650Async + * Protocol: Allstar Firefly + * Key: +--000++- + * + * To register with the SubGHz app, in protocol_list.c add: + * #include "allstar_firefly.h" + * &subghz_protocol_allstar_firefly, (in the protocol array) + */ + +#include "base.h" + +/* Protocol name (must match what is written to .sub files) */ +#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly" + +typedef struct SubGhzProtocolDecoderAllstarFirefly SubGhzProtocolDecoderAllstarFirefly; +typedef struct SubGhzProtocolEncoderAllstarFirefly SubGhzProtocolEncoderAllstarFirefly; + +extern const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder; +extern const SubGhzProtocol subghz_protocol_allstar_firefly; + +/** + * Allocate SubGhzProtocolEncoderAllstarFirefly. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderAllstarFirefly* pointer to a SubGhzProtocolEncoderAllstarFirefly instance + */ +void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderAllstarFirefly. + * @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance + */ +void subghz_protocol_encoder_allstar_firefly_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance + */ +void subghz_protocol_encoder_allstar_firefly_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderAllstarFirefly. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderAllstarFirefly* pointer to a SubGhzProtocolDecoderAllstarFirefly instance + */ +void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderAllstarFirefly. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + */ +void subghz_protocol_decoder_allstar_firefly_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderAllstarFirefly. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + */ +void subghz_protocol_decoder_allstar_firefly_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_allstar_firefly_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderAllstarFirefly. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderAllstarFirefly. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance + * @param output Resulting text + */ +void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 66bd3da31..3e7775b26 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -12,6 +12,9 @@ #define SUBGHZ_NO_ALUTECH_AT_4N_RAINBOW_TABLE 0xFFFFFFFFFFFFFFFF #define SUBGHZ_ALUTECH_AT_4N_RAINBOW_TABLE_SIZE_BYTES 32 +//variable used to bypass CounterMode settings if user just change Counter or Button +static bool bypass = false; + static const SubGhzBlockConst subghz_protocol_alutech_at_4n_const = { .te_short = 400, .te_long = 800, @@ -25,7 +28,6 @@ struct SubGhzProtocolDecoderAlutech_at_4n { SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; - uint64_t data; uint32_t crc; uint16_t header_count; @@ -97,7 +99,7 @@ void* subghz_protocol_encoder_alutech_at_4n_alloc(SubGhzEnvironment* environment instance->base.protocol = &subghz_protocol_alutech_at_4n; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -135,7 +137,7 @@ LevelDuration subghz_protocol_encoder_alutech_at_4n_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -296,9 +298,13 @@ static bool subghz_protocol_alutech_at_4n_gen_data( instance->generic.serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; } - if(alutech_at4n_counter_mode == 0) { + // if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file. + if(subghz_block_generic_global.cnt_need_override) bypass = true; + + if((alutech_at4n_counter_mode == 0) || bypass) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + if((furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) || bypass) { + bypass = false; // standart counter mode. PULL data from subghz_block_generic_global variables if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value @@ -403,6 +409,12 @@ static bool subghz_protocol_encoder_alutech_at_4n_get_upload( btn = subghz_protocol_alutech_at_4n_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) { + bypass = true; + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + } + // Gen new key if(!subghz_protocol_alutech_at_4n_gen_data(instance, btn)) { return false; @@ -580,13 +592,12 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; break; } - if((instance->header_count > 2) && + if((instance->header_count > 9) && (DURATION_DIFF(duration, subghz_protocol_alutech_at_4n_const.te_short * 10) < subghz_protocol_alutech_at_4n_const.te_delta * 10)) { // Found header instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; instance->decoder.decode_data = 0; - instance->data = 0; instance->decoder.decode_count_bit = 0; } else { instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; @@ -619,8 +630,8 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->decoder.parser_step = Alutech_at_4nDecoderStepReset; if(instance->decoder.decode_count_bit == subghz_protocol_alutech_at_4n_const.min_count_bit_for_found) { - if(instance->generic.data != instance->data) { - instance->generic.data = instance->data; + if(instance->generic.data != instance->generic.data_2) { + instance->generic.data = instance->generic.data_2; instance->generic.data_count_bit = instance->decoder.decode_count_bit; instance->crc = instance->decoder.decode_data; @@ -629,7 +640,6 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 instance->base.callback(&instance->base, instance->base.context); } instance->decoder.decode_data = 0; - instance->data = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; } @@ -642,7 +652,7 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 subghz_protocol_alutech_at_4n_const.te_delta * 2)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); if(instance->decoder.decode_count_bit == 64) { - instance->data = instance->decoder.decode_data; + instance->generic.data_2 = instance->decoder.decode_data; instance->decoder.decode_data = 0; } instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; @@ -654,7 +664,7 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 subghz_protocol_alutech_at_4n_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); if(instance->decoder.decode_count_bit == 64) { - instance->data = instance->decoder.decode_data; + instance->generic.data_2 = instance->decoder.decode_data; instance->decoder.decode_data = 0; } instance->decoder.parser_step = Alutech_at_4nDecoderStepSaveDuration; @@ -900,6 +910,10 @@ void subghz_protocol_decoder_alutech_at_4n_get_string(void* context, FuriString* subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; // furi_string_cat_printf( diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index ae523171b..a4d0c7a7e 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -84,7 +84,7 @@ void* subghz_protocol_encoder_ansonic_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_ansonic; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -182,7 +182,7 @@ LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -325,6 +325,13 @@ void subghz_protocol_decoder_ansonic_get_string(void* context, FuriString* outpu furi_assert(context); SubGhzProtocolDecoderAnsonic* instance = context; subghz_protocol_ansonic_check_remote_controller(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 075971ed4..f24ffeaa2 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -98,7 +98,7 @@ static uint8_t subghz_protocol_beninca_arc_get_btn_code(void) { case 0x04: btn = 0x02; break; - case 0xFF: + case 0x00: btn = 0x04; break; @@ -108,12 +108,12 @@ static uint8_t subghz_protocol_beninca_arc_get_btn_code(void) { } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { switch(original_btn_code) { case 0x02: - btn = 0xFF; + btn = 0x00; break; case 0x04: - btn = 0xFF; + btn = 0x00; break; - case 0xFF: + case 0x00: btn = 0x02; break; @@ -258,7 +258,7 @@ void* subghz_protocol_encoder_beninca_arc_alloc(SubGhzEnvironment* environment) instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); - instance->encoder.repeat = 10; + instance->encoder.repeat = 1; instance->encoder.size_upload = 800; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -352,6 +352,10 @@ static void subghz_protocol_beninca_arc_encoder_prepare_packets( // Generate new key using custom or default button instance->generic.btn = subghz_protocol_beninca_arc_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + // Make 3 packets with different mini counter values - 2, 4, 6 for(uint8_t i = 0; i < 3; i++) { subghz_protocol_beninca_arc_encrypt( @@ -479,7 +483,7 @@ LevelDuration subghz_protocol_encoder_beninca_arc_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -661,6 +665,11 @@ void subghz_protocol_decoder_beninca_arc_get_string(void* context, FuriString* o subghz_block_generic_global.cnt_length_bit = 32; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; + // + furi_string_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index a9305b397..4ea79d831 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -93,7 +93,7 @@ void* subghz_protocol_encoder_bett_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_bett; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -201,7 +201,7 @@ LevelDuration subghz_protocol_encoder_bett_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 7dc192a91..cc5549bf4 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -144,7 +144,7 @@ void* subghz_protocol_encoder_bin_raw_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_bin_raw; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = BIN_RAW_BUF_DATA_SIZE * 5; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->data = malloc(instance->encoder.size_upload * sizeof(uint8_t)); @@ -344,7 +344,7 @@ LevelDuration subghz_protocol_encoder_bin_raw_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 940d75f4d..96115755d 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -92,7 +92,7 @@ void* subghz_protocol_encoder_came_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_came; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -213,7 +213,7 @@ LevelDuration subghz_protocol_encoder_came_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index b3141dccb..e7725db40 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -11,6 +11,9 @@ #define TAG "SubGhzProtocoCameAtomo" +//variable used to bypass CounterMode settings if user just change Counter or Button +static bool bypass = false; + static const SubGhzBlockConst subghz_protocol_came_atomo_const = { .te_short = 600, .te_long = 1200, @@ -91,7 +94,7 @@ void* subghz_protocol_encoder_came_atomo_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_came_atomo; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 1; instance->encoder.size_upload = 900; //actual size 766+ instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -189,9 +192,15 @@ static void subghz_protocol_encoder_came_atomo_get_upload( uint8_t pack[8] = {}; - if(came_atomo_counter_mode == 0) { + // if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file. + if(subghz_block_generic_global.cnt_need_override || + subghz_block_generic_global.btn_need_override) + bypass = true; + + if(came_atomo_counter_mode == 0 || bypass) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF || bypass) { + bypass = false; // standart counter mode. PULL data from subghz_block_generic_global variables if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value @@ -251,8 +260,16 @@ static void subghz_protocol_encoder_came_atomo_get_upload( btn = 0x4; } else if(btn == 0x4) { btn = 0x6; + } else if(btn == 0x5) { + btn = 0x0C; + } else if(btn == 0x6) { + btn = 0x0E; } + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + //Send header instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_came_atomo_const.te_long * 15); @@ -432,7 +449,7 @@ LevelDuration subghz_protocol_encoder_came_atomo_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -471,9 +488,11 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t ManchesterEvent event = ManchesterEventReset; switch(instance->decoder.parser_step) { case CameAtomoDecoderStepReset: - // There are two known options for the header: 72K us (TOP42R, TOP44R) or 12k us (found on TOP44RBN) + // There are two known options for the header: 72K us (TOP42R, TOP44R) or 12k us (found on TOP44RBN) / 19k us (TOPD4REN) if((!level) && ((DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 10) < subghz_protocol_came_atomo_const.te_delta * 20) || + (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 16) < + subghz_protocol_came_atomo_const.te_delta * 10) || (DURATION_DIFF(duration, subghz_protocol_came_atomo_const.te_long * 60) < subghz_protocol_came_atomo_const.te_delta * 40))) { //Found header CAME @@ -650,6 +669,10 @@ static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* ins instance->btn = 0x3; } else if(btn_decode == 0x6) { instance->btn = 0x4; + } else if(btn_decode == 0x0C) { + instance->btn = 0x5; + } else if(btn_decode == 0x0E) { + instance->btn = 0x6; } uint32_t hi = pack[0] << 24 | pack[1] << 16 | pack[2] << 8 | pack[3]; @@ -660,7 +683,7 @@ static void subghz_protocol_came_atomo_remote_controller(SubGhzBlockGeneric* ins if(subghz_custom_btn_get_original() == 0) { subghz_custom_btn_set_original(instance->btn); } - subghz_custom_btn_set_max(3); + subghz_custom_btn_set_max(4); } void atomo_encrypt(uint8_t* buff) { @@ -727,6 +750,12 @@ static uint8_t subghz_protocol_came_atomo_get_btn_code(void) { case 0x4: btn = 0x1; break; + case 0x5: + btn = 0x1; + break; + case 0x6: + btn = 0x1; + break; default: break; @@ -745,6 +774,12 @@ static uint8_t subghz_protocol_came_atomo_get_btn_code(void) { case 0x4: btn = 0x2; break; + case 0x5: + btn = 0x2; + break; + case 0x6: + btn = 0x2; + break; default: break; @@ -763,6 +798,36 @@ static uint8_t subghz_protocol_came_atomo_get_btn_code(void) { case 0x4: btn = 0x3; break; + case 0x5: + btn = 0x4; + break; + case 0x6: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x1: + btn = 0x5; + break; + case 0x2: + btn = 0x5; + break; + case 0x3: + btn = 0x5; + break; + case 0x4: + btn = 0x5; + break; + case 0x5: + btn = 0x6; + break; + case 0x6: + btn = 0x5; + break; default: break; @@ -831,6 +896,11 @@ void subghz_protocol_decoder_came_atomo_get_string(void* context, FuriString* ou subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // furi_string_cat_printf( diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 50a3f433b..b5583043a 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -111,8 +111,8 @@ void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_came_twee; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! + instance->encoder.repeat = 1; + instance->encoder.size_upload = 1536; // 1308 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -286,7 +286,7 @@ LevelDuration subghz_protocol_encoder_came_twee_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -324,9 +324,13 @@ void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t ManchesterEvent event = ManchesterEventReset; switch(instance->decoder.parser_step) { case CameTweeDecoderStepReset: - if((!level) && (DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 51) < - subghz_protocol_came_twee_const.te_delta * 20)) { - //Found header CAME + if((!level) && ((DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 51) < + subghz_protocol_came_twee_const.te_delta * 20) || + (DURATION_DIFF(duration, subghz_protocol_came_twee_const.te_long * 12) < + subghz_protocol_came_twee_const.te_delta * 10))) { + // Found header CAME + // Original TWEE uses 51k us delay + // TOP44FGN uses 12k us delay instance->decoder.parser_step = CameTweeDecoderStepDecoderData; instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -446,6 +450,12 @@ void subghz_protocol_decoder_came_twee_get_string(void* context, FuriString* out uint32_t code_found_hi = instance->generic.data >> 32; uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff; + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 6d57b13e7..34ebd329d 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -107,7 +107,7 @@ void* subghz_protocol_encoder_chamb_code_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_chamb_code; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 24; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -256,7 +256,7 @@ LevelDuration subghz_protocol_encoder_chamb_code_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index 054c92a87..bc463616c 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -92,7 +92,7 @@ void* subghz_protocol_encoder_clemsa_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_clemsa; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -201,7 +201,7 @@ LevelDuration subghz_protocol_encoder_clemsa_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -340,10 +340,17 @@ void subghz_protocol_decoder_clemsa_get_string(void* context, FuriString* output SubGhzProtocolDecoderClemsa* instance = context; subghz_protocol_clemsa_check_remote_controller(&instance->generic); //uint32_t data = (uint32_t)(instance->generic.data & 0xFFFFFF); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:%05lX Btn %X\r\n" + "Key:%05lX Btn:%X\r\n" " +: " DIP_PATTERN "\r\n" " o: " DIP_PATTERN "\r\n" " -: " DIP_PATTERN "\r\n", diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index 9e404bc77..f79956b55 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -134,7 +134,7 @@ void* subghz_protocol_encoder_dickert_mahs_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_dickert_mahs; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -239,7 +239,7 @@ LevelDuration subghz_protocol_encoder_dickert_mahs_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/ditec_gol4.c b/lib/subghz/protocols/ditec_gol4.c new file mode 100644 index 000000000..c480bd372 --- /dev/null +++ b/lib/subghz/protocols/ditec_gol4.c @@ -0,0 +1,744 @@ +#include "ditec_gol4.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include "../blocks/custom_btn_i.h" + +#define TAG "SubGhzProtocolDitecGOL4" + +#define GOL4_RAW_BYTES 7 + +static const SubGhzBlockConst subghz_protocol_ditec_gol4_const = { + .te_short = 400, + .te_long = 1100, + .te_delta = 200, + .min_count_bit_for_found = 54, +}; + +struct SubGhzProtocolDecoderDitecGOL4 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderDitecGOL4 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + DitecGOL4DecoderStepReset = 0, + DitecGOL4DecoderStepStartBit, + DitecGOL4DecoderStepSaveDuration, + DitecGOL4DecoderStepCheckDuration, +} DitecGOL4DecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder = { + .alloc = subghz_protocol_decoder_ditec_gol4_alloc, + .free = subghz_protocol_decoder_ditec_gol4_free, + + .feed = subghz_protocol_decoder_ditec_gol4_feed, + .reset = subghz_protocol_decoder_ditec_gol4_reset, + + .get_hash_data = NULL, + .get_hash_data_long = subghz_protocol_decoder_ditec_gol4_get_hash_data, + .serialize = subghz_protocol_decoder_ditec_gol4_serialize, + .deserialize = subghz_protocol_decoder_ditec_gol4_deserialize, + .get_string = subghz_protocol_decoder_ditec_gol4_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder = { + .alloc = subghz_protocol_encoder_ditec_gol4_alloc, + .free = subghz_protocol_encoder_ditec_gol4_free, + + .deserialize = subghz_protocol_encoder_ditec_gol4_deserialize, + .stop = subghz_protocol_encoder_ditec_gol4_stop, + .yield = subghz_protocol_encoder_ditec_gol4_yield, +}; + +const SubGhzProtocol subghz_protocol_ditec_gol4 = { + .name = SUBGHZ_PROTOCOL_DITEC_GOL4_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_ditec_gol4_decoder, + .encoder = &subghz_protocol_ditec_gol4_encoder, +}; + +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0x0 PROG + * @return Button code + */ +static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void); + +static uint8_t gol4_bit_reverse(uint8_t b) { + b &= 0xFF; + uint8_t result = 0; + for(uint8_t i = 0; i < 8; i++) { + result = (uint8_t)((result << 1) | (b & 1)); + b >>= 1; + } + return result; +} + +static uint8_t gol4_bit_parity(uint8_t b) { + uint8_t p = 0; + for(uint8_t i = 0; i < 8; i++) { + if((b >> i) & 1u) p ^= 1u; + } + return p; +} + +static uint8_t gol4_lcg_step(uint8_t seed, uint8_t steps) { + uint8_t x = seed & 0xFF; + steps &= 0xFF; + for(uint8_t i = 0; i < steps; i++) { + x = (uint8_t)((21 * x + 1) & 0xFF); + } + return x; +} + +static uint8_t gol4_lcg_inverse(uint8_t target, uint8_t steps) { + steps &= 0xFF; + if(steps == 0) return target & 0xFF; + return gol4_lcg_step(target, (uint8_t)(256 - steps)); +} + +static void gol4_decode_rotate_and_bitrev(uint8_t* raw) { + uint8_t carry = 0; + for(uint8_t r = 0; r < 3; r++) { + for(uint8_t i = 2; i < 7; i++) { + uint8_t new_carry = raw[i] & 1; + raw[i] = (uint8_t)(((raw[i] >> 1) | (carry << 7)) & 0xFF); + carry = new_carry; + } + } + + raw[0] = gol4_bit_reverse(raw[0]); + raw[1] = gol4_bit_reverse(raw[1]); + raw[3] = gol4_bit_reverse(raw[3]); + raw[4] = gol4_bit_reverse(raw[4]); + + uint8_t b2 = raw[2] & 0xDF; + b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF); + b2 = (uint8_t)((~b2) & 0xFF); + raw[2] = gol4_bit_reverse(b2); + + raw[5] = gol4_bit_reverse(raw[5]); + raw[6] = gol4_bit_reverse(raw[6]); +} + +static bool gol4_decode_lcg_xor(uint8_t* raw) { + if(raw[6] & 0x80) raw[5] ^= 1; + + uint8_t out5 = gol4_lcg_inverse(raw[5], 0xFE); + raw[5] = out5; + + uint8_t out6 = gol4_lcg_inverse(raw[6], raw[5]); + raw[6] = out6; + + raw[5] ^= 0xA7; + raw[6] ^= 0x69; + return true; +} + +static bool gol4_rolling_decode(uint8_t* raw) { + gol4_decode_rotate_and_bitrev(raw); + return gol4_decode_lcg_xor(raw); +} + +static bool gol4_encode_lcg_xor(uint8_t* raw) { + uint8_t dec5 = (uint8_t)(raw[5] ^ 0xA7); + uint8_t dec6 = (uint8_t)(raw[6] ^ 0x69); + + uint8_t enc6 = gol4_lcg_step(dec6, dec5); + uint8_t enc5 = gol4_lcg_step(dec5, 0xFE); + + if(enc6 & 0x80) enc5 ^= 1; + + raw[5] = enc5; + raw[6] = enc6; + return true; +} + +static void gol4_encode_bitrev_and_rotate(uint8_t* raw) { + raw[0] = gol4_bit_reverse(raw[0]); + raw[1] = gol4_bit_reverse(raw[1]); + raw[3] = gol4_bit_reverse(raw[3]); + raw[4] = gol4_bit_reverse(raw[4]); + + if(raw[2] == 0x0) { + raw[2] = 0xF0; + } + uint8_t b2 = gol4_bit_reverse(raw[2]); + b2 = (uint8_t)(~b2); + b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF); + b2 &= 0xDF; + raw[2] = b2; + + raw[5] = gol4_bit_reverse(raw[5]); + raw[6] = gol4_bit_reverse(raw[6]); + + uint8_t p5 = gol4_bit_parity(raw[5]); + uint8_t p6 = gol4_bit_parity(raw[6]); + + uint8_t carry = 0; + for(uint8_t r = 0; r < 3; r++) { + for(int8_t i = 6; i >= 2; i--) { + uint8_t new_carry = (uint8_t)((raw[i] >> 7) & 1); + raw[i] = (uint8_t)(((raw[i] << 1) | carry) & 0xFF); + carry = new_carry; + } + } + + raw[6] = (p5 == p6) ? (uint8_t)(raw[6] & 0xFBu) : (uint8_t)(raw[6] | 0x04u); +} + +static bool gol4_rolling_encode(uint8_t* raw) { + if(!raw) return false; + if(!gol4_encode_lcg_xor(raw)) return false; + gol4_encode_bitrev_and_rotate(raw); + return true; +} + +static void bits_to_raw(const uint8_t* bits, uint8_t* raw) { + memset(raw, 0, GOL4_RAW_BYTES); + for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) { + uint8_t byte_idx = i / 8; + uint8_t bit_idx = 7 - (i % 8); + if(bits[i]) raw[byte_idx] |= (1 << bit_idx); + } +} + +static void raw_to_bits(const uint8_t* raw, uint8_t* bits) { + for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) { + uint8_t byte_idx = i / 8; + uint8_t bit_idx = 7 - (i % 8); + bits[i] = (uint8_t)((raw[byte_idx] >> bit_idx) & 1); + } +} + +static uint64_t bits_to_data(const uint8_t* bits) { + uint64_t data = 0; + for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) { + data = (data << 1) | (uint64_t)(bits[i] & 1); + } + return data; +} + +static uint32_t serial_to_display(const uint8_t* s) { + if(!s) return 0; + return (uint32_t)((s[0] << 24) | (s[4] << 16) | (s[1] << 8) | s[3]); +} + +void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolEncoderDitecGOL4)); + + instance->base.protocol = &subghz_protocol_ditec_gol4; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 4; + instance->encoder.size_upload = 128; // 110 actual + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_ditec_gol4_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderDitecGOL4* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + */ +static void + subghz_protocol_encoder_ditec_gol4_get_upload(SubGhzProtocolEncoderDitecGOL4* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP between repeats + //Send gap before data + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long * 22); + // Start bit + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short * 2); + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_long); + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short); + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_ditec_gol4_decode_key(SubGhzBlockGeneric* instance) { + // Ditec GOL4 Decoder + // 2025 - 2026.02 - @xMasterX (MMX) & @zero-mega + // + // RAW Samples + // 0xCCB2F83208122 - btn 1 = 0011001100101100 101111 100000110010000 01000000100100010 + // + // Programming mode: + // 0xCCB1F832103B9 - btn 0 = 0011001100101100 011111 100000110010000 10000001110111001 + // Regular buttons: + // 0xCCB2F8320ED66 - btn 1 = 0011001100101100 101111 100000110010000 01110110101100110 + // 0xCCB37832104A6 - btn 2 = 0011001100101100 110111 100000110010000 10000010010100110 + // 0xCCB3B8320DB4E - btn 4 = 0011001100101100 111011 100000110010000 01101101101001110 + // 0xCCB3D8320E855 - btn 8 = 0011001100101100 111101 100000110010000 01110100001010101 + // + // Regular buttons: + // Decoded array: CC 34 71 83 09 F8 C1 + // Decoded array: CC 34 71 83 09 F9 C1 + // Decoded array: CC 34 72 83 09 FA C1 + // Decoded array: CC 34 74 83 09 FB C1 + // Decoded array: CC 34 78 83 09 FC C1 + // Programming mode + // Decoded array: CC 34 F0 83 09 FD C1 + // Decoded array: CC 34 F0 83 09 FE C1 + // + uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found]; + uint64_t data = instance->data; + for(int i = subghz_protocol_ditec_gol4_const.min_count_bit_for_found - 1; i >= 0; i--) { + bits[i] = (uint8_t)(data & 1); + data >>= 1; + } + uint8_t decrypted[GOL4_RAW_BYTES]; + bits_to_raw(bits, decrypted); + + if(gol4_rolling_decode(decrypted)) { + uint8_t temp_serial[5]; + memcpy(temp_serial, decrypted, 5); + instance->serial = serial_to_display(temp_serial); + instance->btn = decrypted[2] & 0x0F; + instance->cnt = (uint16_t)((decrypted[5] | (decrypted[6] << 8)) & 0xFFFF); + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(4); + } +} + +static void subghz_protocol_ditec_gol4_encode_key(SubGhzBlockGeneric* instance) { + // Encoder crypto part: + // + // TODO: Current issue - last bit at original remote sometimes 0 but we encode as 1, or vice versa. + // This does not affect decoding but may have issue on real receiver + // + uint8_t decrypted[GOL4_RAW_BYTES]; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + + instance->btn = subghz_protocol_ditec_gol4_get_btn_code(); + + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->btn); + + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get(&instance->cnt)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((instance->cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->cnt = 0; + } else { + instance->cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + if((instance->cnt + 0x1) > 0xFFFF) { + instance->cnt = 0; + } else if(instance->cnt >= 0x1 && instance->cnt != 0xFFFE) { + instance->cnt = 0xFFFE; + } else { + instance->cnt++; + } + } + + decrypted[0] = (uint8_t)((instance->serial >> 24) & 0xFF); + decrypted[4] = (uint8_t)((instance->serial >> 16) & 0xFF); + decrypted[1] = (uint8_t)((instance->serial >> 8) & 0xFF); + decrypted[3] = (uint8_t)(instance->serial & 0xFF); + decrypted[2] = (uint8_t)(instance->btn & 0x0F); + + uint16_t counter = (uint16_t)(instance->cnt & 0xFFFF); + decrypted[5] = (uint8_t)(counter & 0xFF); + decrypted[6] = (uint8_t)((counter >> 8) & 0xFF); + + gol4_rolling_encode(decrypted); + + uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found]; + raw_to_bits(decrypted, bits); + instance->data = bits_to_data(bits); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderDitecGOL4* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_ditec_gol4_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + // Optional parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_ditec_gol4_decode_key(&instance->generic); + subghz_protocol_ditec_gol4_encode_key(&instance->generic); + subghz_protocol_encoder_ditec_gol4_get_upload(instance); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Key"); + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_ditec_gol4_stop(void* context) { + SubGhzProtocolEncoderDitecGOL4* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context) { + SubGhzProtocolEncoderDitecGOL4* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolDecoderDitecGOL4)); + instance->base.protocol = &subghz_protocol_ditec_gol4; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_ditec_gol4_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + free(instance); +} + +void subghz_protocol_decoder_ditec_gol4_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + instance->decoder.parser_step = DitecGOL4DecoderStepReset; +} + +void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + + switch(instance->decoder.parser_step) { + case DitecGOL4DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long * 22) < + (subghz_protocol_ditec_gol4_const.te_long * 4))) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = DitecGOL4DecoderStepStartBit; + } + break; + + case DitecGOL4DecoderStepStartBit: + if((level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short * 2) < + subghz_protocol_ditec_gol4_const.te_delta)) { + instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = DitecGOL4DecoderStepReset; + } + break; + + case DitecGOL4DecoderStepSaveDuration: + if(!level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = DitecGOL4DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = DitecGOL4DecoderStepReset; + } + break; + + case DitecGOL4DecoderStepCheckDuration: + if(level) { + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_short) < + subghz_protocol_ditec_gol4_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long) < + subghz_protocol_ditec_gol4_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long) < + subghz_protocol_ditec_gol4_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short) < + subghz_protocol_ditec_gol4_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration; + } + } else { + if(DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long * 20) < + (subghz_protocol_ditec_gol4_const.te_long * 3)) { + if(instance->decoder.decode_count_bit == + subghz_protocol_ditec_gol4_const.min_count_bit_for_found) { + // 54 bits received, save and continue + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + subghz_protocol_ditec_gol4_const.min_count_bit_for_found; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = DitecGOL4DecoderStepReset; + } else { + instance->decoder.parser_step = DitecGOL4DecoderStepReset; + } + } + break; + } +} + +uint32_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_ditec_gol4_const.min_count_bit_for_found); +} + +bool subghz_protocol_ditec_gol4_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderDitecGOL4* instance = context; + instance->generic.btn = btn; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = subghz_protocol_ditec_gol4_const.min_count_bit_for_found; + + subghz_protocol_ditec_gol4_encode_key(&instance->generic); + + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + case 0x0: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + case 0x0: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + case 0x0: + btn = 0x2; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x1: + btn = 0x0; + break; + case 0x2: + btn = 0x0; + break; + case 0x4: + btn = 0x0; + break; + case 0x8: + btn = 0x0; + break; + case 0x0: + btn = 0x8; + break; + + default: + break; + } + } + + return btn; +} + +void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + + subghz_protocol_ditec_gol4_decode_key(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.cnt_is_available = true; + subghz_block_generic_global.cnt_length_bit = 16; + subghz_block_generic_global.current_cnt = instance->generic.cnt; + + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key:0x%0lX%08lX\r\n" + "Serial:0x%08lX\r\n" + "Btn:%01X %s\r\n" + "Cnt:%04lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data & 0xFFFFFFFF), + instance->generic.serial, + instance->generic.btn, + (instance->generic.btn == 0x0) ? "- Prog" : "", + instance->generic.cnt); +} diff --git a/lib/subghz/protocols/ditec_gol4.h b/lib/subghz/protocols/ditec_gol4.h new file mode 100644 index 000000000..37fb138db --- /dev/null +++ b/lib/subghz/protocols/ditec_gol4.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_DITEC_GOL4_NAME "Ditec GOL4" + +typedef struct SubGhzProtocolDecoderDitecGOL4 SubGhzProtocolDecoderDitecGOL4; +typedef struct SubGhzProtocolEncoderDitecGOL4 SubGhzProtocolEncoderDitecGOL4; + +extern const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder; +extern const SubGhzProtocol subghz_protocol_ditec_gol4; + +/** + * Allocate SubGhzProtocolEncoderDitecGOL4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderDitecGOL4* pointer to a SubGhzProtocolEncoderDitecGOL4 instance + */ +void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderDitecGOL4. + * @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + */ +void subghz_protocol_encoder_ditec_gol4_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + */ +void subghz_protocol_encoder_ditec_gol4_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderDitecGOL4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderDitecGOL4* pointer to a SubGhzProtocolDecoderDitecGOL4 instance + */ +void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderDitecGOL4. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + */ +void subghz_protocol_decoder_ditec_gol4_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderDitecGOL4. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + */ +void subghz_protocol_decoder_ditec_gol4_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderDitecGOL4. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderDitecGOL4. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance + * @param output Resulting text + */ +void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 358eaa319..1bd2371fc 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -84,7 +84,7 @@ void* subghz_protocol_encoder_doitrand_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_doitrand; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -181,7 +181,7 @@ LevelDuration subghz_protocol_encoder_doitrand_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -335,6 +335,13 @@ void subghz_protocol_decoder_doitrand_get_string(void* context, FuriString* outp furi_assert(context); SubGhzProtocolDecoderDoitrand* instance = context; subghz_protocol_doitrand_check_remote_controller(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index dcfe8f50c..b00e3f30f 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -79,7 +79,7 @@ void* subghz_protocol_encoder_dooya_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_dooya; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -191,7 +191,7 @@ LevelDuration subghz_protocol_encoder_dooya_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -420,16 +420,23 @@ void subghz_protocol_decoder_dooya_get_string(void* context, FuriString* output) subghz_protocol_dooya_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; + // + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%010llX\r\n" "Sn:0x%08lX\r\n" - "Btn:%s\r\n", + "Btn:%X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, instance->generic.data, instance->generic.serial, + instance->generic.btn, subghz_protocol_dooya_get_name_button(instance->generic.btn)); if(instance->generic.cnt == DOYA_SINGLE_CHANNEL) { furi_string_cat_printf(output, "Ch:Single\r\n"); diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c index b50d51544..4c645c790 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -75,8 +75,8 @@ void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_elplast; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 64; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -170,7 +170,7 @@ LevelDuration subghz_protocol_encoder_elplast_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 47a05e381..fbb14c05c 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -111,7 +111,7 @@ void* subghz_protocol_encoder_faac_slh_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -125,122 +125,154 @@ void subghz_protocol_encoder_faac_slh_free(void* context) { free(instance); } -static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* instance) { - // TODO: Stupid bypass for custom button, remake later - if(subghz_custom_btn_get_original() == 0) { - subghz_custom_btn_set_original(0xF); - } - - uint8_t custom_btn_id = subghz_custom_btn_get(); - - // If we are using UP button - generate programming mode key and send it, otherwise - send regular key if possible - if((custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) && - !(!allow_zero_seed && (instance->generic.seed == 0x0))) { - uint8_t data_tmp = 0; - uint8_t data_prg[8]; - - data_prg[0] = 0x00; - - if(allow_zero_seed || (instance->generic.seed != 0x0)) { - // check OFEX mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { - // standart counter mode. PULL data from subghz_block_generic_global variables - if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { - // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > - 0xFFFFF) { - instance->generic.cnt = 0; - } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); - } - } - } else { - // TODO: OFEX mode - instance->generic.cnt += 1; - } - - if(temp_counter_backup != 0x0) { - // check OFEX mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { - // standart counter mode. PULL data from subghz_block_generic_global variables - if(!subghz_block_generic_global_counter_override_get(&temp_counter_backup)) { - // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value - if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > - 0xFFFFF) { - temp_counter_backup = 0; - } else { - temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult(); - } - } - } else { - // todo OFEX mode - temp_counter_backup += 1; - } - } - } - - data_prg[1] = instance->generic.cnt & 0xFF; - - data_prg[2] = (uint8_t)(instance->generic.seed & 0xFF); - data_prg[3] = (uint8_t)(instance->generic.seed >> 8 & 0xFF); - data_prg[4] = (uint8_t)(instance->generic.seed >> 16 & 0xFF); - data_prg[5] = (uint8_t)(instance->generic.seed >> 24); - - data_prg[2] ^= data_prg[1]; - data_prg[3] ^= data_prg[1]; - data_prg[4] ^= data_prg[1]; - data_prg[5] ^= data_prg[1]; - - for(uint8_t i = data_prg[1] & 0x0F; i != 0; i--) { - data_tmp = data_prg[5]; - - data_prg[5] = ((data_prg[5] << 1) & 0xFF) | (data_prg[4] & 0x80) >> 7; - data_prg[4] = ((data_prg[4] << 1) & 0xFF) | (data_prg[3] & 0x80) >> 7; - data_prg[3] = ((data_prg[3] << 1) & 0xFF) | (data_prg[2] & 0x80) >> 7; - data_prg[2] = ((data_prg[2] << 1) & 0xFF) | (data_tmp & 0x80) >> 7; - } - data_prg[6] = 0x0F; - data_prg[7] = 0x52; - - uint32_t enc_prg_1 = data_prg[7] << 24 | data_prg[6] << 16 | data_prg[5] << 8 | - data_prg[4]; - uint32_t enc_prg_2 = data_prg[3] << 24 | data_prg[2] << 16 | data_prg[1] << 8 | - data_prg[0]; - instance->generic.data = (uint64_t)enc_prg_1 << 32 | enc_prg_2; - //FURI_LOG_D(TAG, "New Prog Mode Key Generated: %016llX\r", instance->generic.data); - - return true; - } else { - if(!allow_zero_seed && (instance->generic.seed == 0x0)) { - // Do not generate new data, send data from buffer - return true; - } - // If we are in prog mode and regular Send button is used - Do not generate new data, send data from buffer - if((faac_prog_mode == true) && (instance->generic.serial == 0x0) && - (instance->generic.btn == 0x0) && (temp_fix_backup == 0x0)) { - return true; - } - } - // Restore main remote data when we exit programming mode - if((instance->generic.serial == 0x0) && (instance->generic.btn == 0x0) && - (temp_fix_backup != 0x0) && !faac_prog_mode) { - instance->generic.serial = temp_fix_backup >> 4; - instance->generic.btn = temp_fix_backup & 0xF; - if(temp_counter_backup != 0x0) { - instance->generic.cnt = temp_counter_backup; - } - } +static bool subghz_protocol_faac_slh_encrypt(SubGhzProtocolEncoderFaacSLH* instance) { uint32_t fix = instance->generic.serial << 4 | instance->generic.btn; uint32_t hop = 0; uint32_t decrypt = 0; uint64_t man = 0; - int res = 0; char fixx[8] = {}; int shiftby = 32; + for(int i = 0; i < 8; i++) { fixx[i] = (fix >> (shiftby -= 4)) & 0xF; } + if((instance->generic.cnt % 2) == 0) { + decrypt = fixx[6] << 28 | fixx[7] << 24 | fixx[5] << 20 | + (instance->generic.cnt & 0xFFFFF); + } else { + decrypt = fixx[2] << 28 | fixx[3] << 24 | fixx[4] << 20 | + (instance->generic.cnt & 0xFFFFF); + } + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + if(strcmp(furi_string_get_cstr(manufacture_code->name), "FAAC_SLH") == 0) { + //FAAC Learning + man = subghz_protocol_keeloq_common_faac_learning( + instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + } + } + if(hop) { + instance->generic.data = (uint64_t)fix << 32 | hop; + } + return true; +} + +static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* instance) { + // override button if we change it with signal settings button editor + // else work as standart + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) { + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + } else { + // TODO: Stupid bypass for custom button, remake later + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(0xF); + } + + uint8_t custom_btn_id = subghz_custom_btn_get(); + + // If we are using UP button - generate programming mode key and send it, otherwise - send regular key if possible + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) && + !(!allow_zero_seed && (instance->generic.seed == 0x0))) { + uint8_t data_tmp = 0; + uint8_t data_prg[8]; + + data_prg[0] = 0x00; + + if(allow_zero_seed || (instance->generic.seed != 0x0)) { + // check OFEX mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + // TODO: OFEX mode + instance->generic.cnt += 1; + } + + if(temp_counter_backup != 0x0) { + // check OFEX mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get( + &temp_counter_backup)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > + 0xFFFFF) { + temp_counter_backup = 0; + } else { + temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + // todo OFEX mode + temp_counter_backup += 1; + } + } + } + + data_prg[1] = instance->generic.cnt & 0xFF; + + data_prg[2] = (uint8_t)(instance->generic.seed & 0xFF); + data_prg[3] = (uint8_t)(instance->generic.seed >> 8 & 0xFF); + data_prg[4] = (uint8_t)(instance->generic.seed >> 16 & 0xFF); + data_prg[5] = (uint8_t)(instance->generic.seed >> 24); + + data_prg[2] ^= data_prg[1]; + data_prg[3] ^= data_prg[1]; + data_prg[4] ^= data_prg[1]; + data_prg[5] ^= data_prg[1]; + + for(uint8_t i = data_prg[1] & 0x0F; i != 0; i--) { + data_tmp = data_prg[5]; + + data_prg[5] = ((data_prg[5] << 1) & 0xFF) | (data_prg[4] & 0x80) >> 7; + data_prg[4] = ((data_prg[4] << 1) & 0xFF) | (data_prg[3] & 0x80) >> 7; + data_prg[3] = ((data_prg[3] << 1) & 0xFF) | (data_prg[2] & 0x80) >> 7; + data_prg[2] = ((data_prg[2] << 1) & 0xFF) | (data_tmp & 0x80) >> 7; + } + data_prg[6] = 0x0F; + data_prg[7] = 0x52; + + uint32_t enc_prg_1 = data_prg[7] << 24 | data_prg[6] << 16 | data_prg[5] << 8 | + data_prg[4]; + uint32_t enc_prg_2 = data_prg[3] << 24 | data_prg[2] << 16 | data_prg[1] << 8 | + data_prg[0]; + instance->generic.data = (uint64_t)enc_prg_1 << 32 | enc_prg_2; + //FURI_LOG_D(TAG, "New Prog Mode Key Generated: %016llX\r", instance->generic.data); + + return true; + } else { + if(!allow_zero_seed && (instance->generic.seed == 0x0)) { + // Do not generate new data, send data from buffer + return true; + } + // If we are in prog mode and regular Send button is used - Do not generate new data, send data from buffer + if((faac_prog_mode == true) && (instance->generic.serial == 0x0) && + (instance->generic.btn == 0x0) && (temp_fix_backup == 0x0)) { + return true; + } + } + // Restore main remote data when we exit programming mode + if((instance->generic.serial == 0x0) && (instance->generic.btn == 0x0) && + (temp_fix_backup != 0x0) && !faac_prog_mode) { + instance->generic.serial = temp_fix_backup >> 4; + instance->generic.btn = temp_fix_backup & 0xF; + if(temp_counter_backup != 0x0) { + instance->generic.cnt = temp_counter_backup; + } + } + } + if(allow_zero_seed || (instance->generic.seed != 0x0)) { // check OFEX mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { @@ -270,32 +302,7 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst } } - if((instance->generic.cnt % 2) == 0) { - decrypt = fixx[6] << 28 | fixx[7] << 24 | fixx[5] << 20 | - (instance->generic.cnt & 0xFFFFF); - } else { - decrypt = fixx[2] << 28 | fixx[3] << 24 | fixx[4] << 20 | - (instance->generic.cnt & 0xFFFFF); - } - for - M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - res = strcmp(furi_string_get_cstr(manufacture_code->name), instance->manufacture_name); - if(res == 0) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_FAAC: - //FAAC Learning - man = subghz_protocol_keeloq_common_faac_learning( - instance->generic.seed, manufacture_code->key); - hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); - break; - } - break; - } - } - if(hop) { - instance->generic.data = (uint64_t)fix << 32 | hop; - } - return true; + return subghz_protocol_faac_slh_encrypt(instance); } bool subghz_protocol_faac_slh_create_data( @@ -317,7 +324,7 @@ bool subghz_protocol_faac_slh_create_data( instance->manufacture_name = manufacture_name; instance->generic.data_count_bit = 64; allow_zero_seed = true; - bool res = subghz_protocol_faac_slh_gen_data(instance); + bool res = subghz_protocol_faac_slh_encrypt(instance); if(res) { return SubGhzProtocolStatusOk == subghz_block_generic_serialize(&instance->generic, flipper_format, preset); @@ -443,7 +450,7 @@ LevelDuration subghz_protocol_encoder_faac_slh_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -613,8 +620,7 @@ static void subghz_protocol_faac_slh_check_remote_controller( for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - switch(manufacture_code->type) { - case KEELOQ_LEARNING_FAAC: + if(strcmp(furi_string_get_cstr(manufacture_code->name), "FAAC_SLH") == 0) { // FAAC Learning man = subghz_protocol_keeloq_common_faac_learning( instance->seed, manufacture_code->key); @@ -738,6 +744,11 @@ void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* outp instance->generic.seed, (uint8_t)(instance->generic.cnt & 0xFF)); } else if((allow_zero_seed == false) && (instance->generic.seed == 0x0)) { + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // furi_string_cat_printf( output, "%s %dbit\r\n" @@ -758,6 +769,10 @@ void subghz_protocol_decoder_faac_slh_get_string(void* context, FuriString* outp subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 20; subghz_block_generic_global.current_cnt = instance->generic.cnt; + + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; // furi_string_cat_printf( diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index 0d0ed6a47..76a0e72a5 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -77,7 +77,7 @@ void* subghz_protocol_encoder_feron_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_feron; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -169,7 +169,6 @@ SubGhzProtocolStatus subghz_protocol_feron_check_remote_controller(&instance->generic); subghz_protocol_encoder_feron_get_upload(instance); - instance->encoder.front = 0; instance->encoder.is_running = true; } while(false); @@ -179,7 +178,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_feron_stop(void* context) { SubGhzProtocolEncoderFeron* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_feron_yield(void* context) { @@ -193,7 +191,7 @@ LevelDuration subghz_protocol_encoder_feron_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index b08c65320..cdef6e7f6 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -79,8 +79,8 @@ void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_gangqi; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 1024; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -172,6 +172,10 @@ static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQ // Generate new key using custom or default button instance->generic.btn = subghz_protocol_gangqi_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + uint16_t serial = (uint16_t)((instance->generic.data >> 18) & 0xFFFF); uint8_t const_and_button = (uint8_t)(0xD0 | instance->generic.btn); uint8_t serial_high = (uint8_t)(serial >> 8); @@ -183,40 +187,35 @@ static void subghz_protocol_encoder_gangqi_get_upload(SubGhzProtocolEncoderGangQ size_t index = 0; - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long * 2); - - for(size_t r = 0; r < 5; r++) { - // Send key and GAP between parcels - for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { - if(bit_read(instance->generic.data, i - 1)) { - // Send bit 1 - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long); - if(i == 1) { - //Send gap if bit was last - instance->encoder.upload[index++] = level_duration_make( - false, - (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + - subghz_protocol_gangqi_const.te_delta); - } else { - instance->encoder.upload[index++] = level_duration_make( - false, (uint32_t)subghz_protocol_gangqi_const.te_short); - } + // Send key and GAP between parcels + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + + subghz_protocol_gangqi_const.te_delta); } else { - // Send bit 0 instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short); - if(i == 1) { - //Send gap if bit was last - instance->encoder.upload[index++] = level_duration_make( - false, - (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + - subghz_protocol_gangqi_const.te_delta); - } else { - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long); - } + level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_gangqi_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_gangqi_const.te_short * 4 + + subghz_protocol_gangqi_const.te_delta); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_gangqi_const.te_long); } } } @@ -267,7 +266,6 @@ SubGhzProtocolStatus subghz_protocol_gangqi_remote_controller(&instance->generic); subghz_protocol_encoder_gangqi_get_upload(instance); - instance->encoder.front = 0; if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); @@ -291,7 +289,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_gangqi_stop(void* context) { SubGhzProtocolEncoderGangQi* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) { @@ -305,7 +302,7 @@ LevelDuration subghz_protocol_encoder_gangqi_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -339,7 +336,7 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin switch(instance->decoder.parser_step) { case GangQiDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < - subghz_protocol_gangqi_const.te_delta * 5)) { + subghz_protocol_gangqi_const.te_delta * 3)) { //Found GAP instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -373,8 +370,8 @@ void subghz_protocol_decoder_gangqi_feed(void* context, bool level, volatile uin instance->decoder.parser_step = GangQiDecoderStepSaveDuration; } else if( // End of the key - DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < - subghz_protocol_gangqi_const.te_delta * 5) { + (DURATION_DIFF(duration, subghz_protocol_gangqi_const.te_long * 2) < + subghz_protocol_gangqi_const.te_delta * 3)) { //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_gangqi_const.te_short) < @@ -471,6 +468,12 @@ void subghz_protocol_decoder_gangqi_get_string(void* context, FuriString* output uint8_t sum_type1 = (uint8_t)(0xC8 - serial_high - serial_low - const_and_button); uint8_t sum_type2 = (uint8_t)(0x02 + serial_high + serial_low + const_and_button); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 140563a86..659f911f2 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -77,7 +77,7 @@ void* subghz_protocol_encoder_gate_tx_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_gate_tx; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -174,7 +174,7 @@ LevelDuration subghz_protocol_encoder_gate_tx_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -313,6 +313,13 @@ void subghz_protocol_decoder_gate_tx_get_string(void* context, FuriString* outpu furi_assert(context); SubGhzProtocolDecoderGateTx* instance = context; subghz_protocol_gate_tx_check_remote_controller(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 6dd996b50..013b7fbd4 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -77,8 +77,8 @@ void* subghz_protocol_encoder_hay21_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_hay21; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 64; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -146,6 +146,10 @@ static void subghz_protocol_encoder_hay21_get_upload(SubGhzProtocolEncoderHay21* // Generate new key using custom or default button instance->generic.btn = subghz_protocol_hay21_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + // Counter increment // Check for OFEX (overflow experimental) mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { @@ -311,7 +315,7 @@ LevelDuration subghz_protocol_encoder_hay21_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -470,6 +474,11 @@ void subghz_protocol_decoder_hay21_get_string(void* context, FuriString* output) subghz_block_generic_global.cnt_length_bit = 8; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; + // + furi_string_cat_printf( output, "%s - %dbit\r\n" diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index f8b589fb7..0a5a6ab35 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -79,8 +79,8 @@ void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_hollarm; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -171,6 +171,10 @@ static void subghz_protocol_encoder_hollarm_get_upload(SubGhzProtocolEncoderHoll // Generate new key using custom or default button instance->generic.btn = subghz_protocol_hollarm_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + uint64_t new_key = (instance->generic.data >> 12) << 12 | (instance->generic.btn << 8); uint8_t bytesum = ((new_key >> 32) & 0xFF) + ((new_key >> 24) & 0xFF) + @@ -263,7 +267,6 @@ SubGhzProtocolStatus subghz_protocol_hollarm_remote_controller(&instance->generic); subghz_protocol_encoder_hollarm_get_upload(instance); - instance->encoder.front = 0; if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); @@ -287,7 +290,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_hollarm_stop(void* context) { SubGhzProtocolEncoderHollarm* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) { @@ -301,7 +303,7 @@ LevelDuration subghz_protocol_encoder_hollarm_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -473,6 +475,12 @@ void subghz_protocol_decoder_hollarm_get_string(void* context, FuriString* outpu ((instance->generic.data >> 32) & 0xFF) + ((instance->generic.data >> 24) & 0xFF) + ((instance->generic.data >> 16) & 0xFF) + ((instance->generic.data >> 8) & 0xFF); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 4f23b87ee..ac571e542 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -88,7 +88,7 @@ void* subghz_protocol_encoder_holtek_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_holtek; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -187,7 +187,7 @@ LevelDuration subghz_protocol_encoder_holtek_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -346,6 +346,12 @@ void subghz_protocol_decoder_holtek_get_string(void* context, FuriString* output SubGhzProtocolDecoderHoltek* instance = context; subghz_protocol_holtek_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 47d4addd9..0fab15bc4 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -8,7 +8,7 @@ /* * Help - * https://www.holtek.com/documents/10179/116711/HT12A_Ev130.pdf + * https://www.holtek.com/webapi/116711/HT12A_Ev130.pdf * */ @@ -96,7 +96,7 @@ void* subghz_protocol_encoder_holtek_th12x_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_holtek_th12x; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -202,7 +202,7 @@ LevelDuration subghz_protocol_encoder_holtek_th12x_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -391,6 +391,12 @@ void subghz_protocol_decoder_holtek_th12x_get_string(void* context, FuriString* SubGhzProtocolDecoderHoltek_HT12X* instance = context; subghz_protocol_holtek_th12x_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index ce72b3d80..fa1a39b62 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -246,7 +246,7 @@ LevelDuration subghz_protocol_encoder_honeywell_yield(void* context) { } LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } return ret; diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 132aa15d5..448e88ce3 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -92,7 +92,7 @@ void* subghz_protocol_encoder_honeywell_wdb_alloc(SubGhzEnvironment* environment instance->base.protocol = &subghz_protocol_honeywell_wdb; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -190,7 +190,7 @@ LevelDuration subghz_protocol_encoder_honeywell_wdb_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 80e5efb60..c9c6caf7d 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -82,8 +82,8 @@ void* subghz_protocol_encoder_hormann_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_hormann; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 2048; + instance->encoder.repeat = 1; + instance->encoder.size_upload = 1850; // 1801 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -112,7 +112,6 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm } else { instance->encoder.size_upload = size_upload; } - instance->encoder.repeat = 10; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { //Send start bit @@ -139,6 +138,7 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm } instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)subghz_protocol_hormann_const.te_short * 24); + return true; } @@ -187,7 +187,7 @@ LevelDuration subghz_protocol_encoder_hormann_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -321,6 +321,12 @@ void subghz_protocol_decoder_hormann_get_string(void* context, FuriString* outpu SubGhzProtocolDecoderHormann* instance = context; subghz_protocol_hormann_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s\r\n" diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 3fcda27c7..5837f1caa 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -209,6 +209,12 @@ void subghz_protocol_decoder_ido_get_string(void* context, FuriString* output) { uint32_t code_fix = code_found_reverse & 0xFFFFFF; uint32_t code_hop = (code_found_reverse >> 24) & 0xFFFFFF; + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index adc64853d..8caa035a1 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -88,7 +88,7 @@ void* subghz_protocol_encoder_intertechno_v3_alloc(SubGhzEnvironment* environmen instance->base.protocol = &subghz_protocol_intertechno_v3; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -209,7 +209,7 @@ LevelDuration subghz_protocol_encoder_intertechno_v3_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -446,6 +446,10 @@ void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString subghz_protocol_intertechno_v3_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.current_btn = instance->generic.btn; + // + furi_string_cat_printf( output, "%.11s %db\r\n" @@ -461,12 +465,16 @@ void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString if(instance->generic.cnt >> 5) { furi_string_cat_printf( output, "Ch: All Btn:%s\r\n", (instance->generic.btn ? "On" : "Off")); + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.btn_length_bit = 1; } else { furi_string_cat_printf( output, "Ch:" CH_PATTERN " Btn:%s\r\n", CNT_TO_CH(instance->generic.cnt), (instance->generic.btn ? "On" : "Off")); + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.btn_length_bit = 1; } } else if(instance->generic.data_count_bit == INTERTECHNO_V3_DIMMING_COUNT_BIT) { furi_string_cat_printf( @@ -474,5 +482,7 @@ void subghz_protocol_decoder_intertechno_v3_get_string(void* context, FuriString "Ch:" CH_PATTERN " Dimm:%d%%\r\n", CNT_TO_CH(instance->generic.cnt), (int)(6.67f * (float)instance->generic.btn)); + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.btn_length_bit = 4; } } diff --git a/lib/subghz/protocols/jarolift.c b/lib/subghz/protocols/jarolift.c new file mode 100644 index 000000000..fa42adc24 --- /dev/null +++ b/lib/subghz/protocols/jarolift.c @@ -0,0 +1,788 @@ +#include "jarolift.h" +#include "core/log.h" +#include "keeloq_common.h" + +#include "../subghz_keystore.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#include "../blocks/custom_btn_i.h" + +#define TAG "SubGhzProtocolJarolift" + +static const SubGhzBlockConst subghz_protocol_jarolift_const = { + .te_short = 400, + .te_long = 800, + .te_delta = 167, + .min_count_bit_for_found = 72, +}; + +struct SubGhzProtocolDecoderJarolift { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t header_count; + SubGhzKeystore* keystore; +}; + +struct SubGhzProtocolEncoderJarolift { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + SubGhzKeystore* keystore; +}; + +typedef enum { + JaroliftDecoderStepReset = 0, + JaroliftDecoderStepCheckPreambula, + JaroliftDecoderStepSaveDuration, + JaroliftDecoderStepCheckDuration, +} JaroliftDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_jarolift_decoder = { + .alloc = subghz_protocol_decoder_jarolift_alloc, + .free = subghz_protocol_decoder_jarolift_free, + + .feed = subghz_protocol_decoder_jarolift_feed, + .reset = subghz_protocol_decoder_jarolift_reset, + + .get_hash_data = NULL, + .get_hash_data_long = subghz_protocol_decoder_jarolift_get_hash_data, + .serialize = subghz_protocol_decoder_jarolift_serialize, + .deserialize = subghz_protocol_decoder_jarolift_deserialize, + .get_string = subghz_protocol_decoder_jarolift_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder subghz_protocol_jarolift_encoder = { + .alloc = subghz_protocol_encoder_jarolift_alloc, + .free = subghz_protocol_encoder_jarolift_free, + + .deserialize = subghz_protocol_encoder_jarolift_deserialize, + .stop = subghz_protocol_encoder_jarolift_stop, + .yield = subghz_protocol_encoder_jarolift_yield, +}; + +const SubGhzProtocol subghz_protocol_jarolift = { + .name = SUBGHZ_PROTOCOL_JAROLIFT_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_jarolift_decoder, + .encoder = &subghz_protocol_jarolift_encoder, +}; + +// +// Encoder +// + +// Pre define function +static void subghz_protocol_jarolift_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore); + +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | + * @return Button code + */ +static uint8_t subghz_protocol_jarolift_get_btn_code(void); + +void* subghz_protocol_encoder_jarolift_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolEncoderJarolift* instance = malloc(sizeof(SubGhzProtocolEncoderJarolift)); + + instance->base.protocol = &subghz_protocol_jarolift; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + + instance->encoder.repeat = 3; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + + return instance; +} + +void subghz_protocol_encoder_jarolift_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void subghz_protocol_encoder_jarolift_stop(void* context) { + SubGhzProtocolEncoderJarolift* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_jarolift_yield(void* context) { + SubGhzProtocolEncoderJarolift* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +/** + * Key generation from simple data + * @param instance Pointer to a SubGhzProtocolEncoderJarolift* instance + * @param btn Button number, 4 bit + */ +static bool + subghz_protocol_jarolift_gen_data(SubGhzProtocolEncoderJarolift* instance, uint8_t btn) { + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + btn = subghz_protocol_jarolift_get_btn_code(); + + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + if((instance->generic.cnt + 0x1) > 0xFFFF) { + instance->generic.cnt = 0; + } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { + instance->generic.cnt = 0xFFFE; + } else { + instance->generic.cnt++; + } + } + + //(instance->generic.seed >> 8) = 8 bit grouping channel 0-7 + uint32_t hop_decrypted = (uint64_t)((instance->generic.seed >> 8) & 0xFF) << 24 | + ((instance->generic.serial & 0xFF) << 16) | + (instance->generic.cnt & 0xFFFF); + + uint64_t hop_encrypted = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + // Normal Learning + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->generic.serial, manufacture_code->key); + hop_encrypted = subghz_protocol_keeloq_common_encrypt(hop_decrypted, man); + break; + } + } + + // If we got some issue, return false + if(hop_encrypted == 0) { + return false; + } + uint64_t fix = (uint64_t)btn << 60 | ((uint64_t)(instance->generic.serial & 0xFFFFFFF) << 32) | + hop_encrypted; + + instance->generic.data = subghz_protocol_blocks_reverse_key(fix, 64); + //(instance->generic.seed & 0xFF) = 8 bit for grouping 8-16 + instance->generic.data_2 = + subghz_protocol_blocks_reverse_key((instance->generic.seed & 0xFF), 8); + + return true; +} + +bool subghz_protocol_jarolift_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + instance->generic.serial = (serial & 0xFFFFF00); + instance->generic.cnt = cnt; + instance->generic.btn = btn; + instance->generic.seed = 0x0100; + instance->generic.data_count_bit = 72; + + // Encode data + + //(instance->generic.seed >> 8) = 8 bit grouping channel 0-7 + uint32_t hop_decrypted = (uint64_t)((instance->generic.seed >> 8) & 0xFF) << 24 | + ((instance->generic.serial & 0xFF) << 16) | + (instance->generic.cnt & 0xFFFF); + + uint64_t hop_encrypted = 0; + for + M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + // Normal Learning + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->generic.serial, manufacture_code->key); + hop_encrypted = subghz_protocol_keeloq_common_encrypt(hop_decrypted, man); + break; + } + } + + uint64_t fix = (uint64_t)instance->generic.btn << 60 | + ((uint64_t)(instance->generic.serial & 0xFFFFFFF) << 32) | hop_encrypted; + + instance->generic.data = subghz_protocol_blocks_reverse_key(fix, 64); + //(instance->generic.seed & 0xFF) = 8 bit for grouping 8-16 + instance->generic.data_2 = + subghz_protocol_blocks_reverse_key((instance->generic.seed & 0xFF), 8); + + // Encode complete, now serialize + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + if((res == SubGhzProtocolStatusOk) && + !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data2"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + return res == SubGhzProtocolStatusOk; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderJarolift instance + * @return true On success + */ +static bool subghz_protocol_encoder_jarolift_get_upload( + SubGhzProtocolEncoderJarolift* instance, + uint8_t btn) { + furi_assert(instance); + + // Gen new key + if(!subghz_protocol_jarolift_gen_data(instance, btn)) { + return false; + } + + size_t index = 0; + + // Start 14k us delay + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long * 18); + + // First header bit + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)1500); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + + // Finish header + for(uint8_t i = 8; i > 0; i--) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + + // After header + instance->encoder.upload[index - 1].duration = (uint32_t)3800; // Adjust last low duration + + // Send key fix + for(uint8_t i = 64; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + } + + // Send grouping byte + for(uint8_t i = 8; i > 0; i--) { + if(bit_read(instance->generic.data_2, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_jarolift_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_jarolift_const.te_short); + } + } + + // Set upload size after generating upload, fix it later + instance->encoder.size_upload = index; + + return true; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderJarolift* instance = context; + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + do { + if(SubGhzProtocolStatusOk != + subghz_block_generic_deserialize(&instance->generic, flipper_format)) { + FURI_LOG_E(TAG, "Deserialize error"); + break; + } + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + + subghz_protocol_jarolift_remote_controller(&instance->generic, instance->keystore); + + subghz_protocol_encoder_jarolift_get_upload(instance, instance->generic.btn); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Key"); + break; + } + + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> i * 8) & 0xFF; + } + if(!flipper_format_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to update Data"); + break; + } + + instance->encoder.is_running = true; + + res = SubGhzProtocolStatusOk; + } while(false); + + return res; +} + +// +// Decoder +// +void* subghz_protocol_decoder_jarolift_alloc(SubGhzEnvironment* environment) { + SubGhzProtocolDecoderJarolift* instance = malloc(sizeof(SubGhzProtocolDecoderJarolift)); + instance->base.protocol = &subghz_protocol_jarolift; + instance->generic.protocol_name = instance->base.protocol->name; + instance->keystore = subghz_environment_get_keystore(environment); + return instance; +} + +void subghz_protocol_decoder_jarolift_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + free(instance); +} + +void subghz_protocol_decoder_jarolift_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + instance->decoder.parser_step = JaroliftDecoderStepReset; +} + +void subghz_protocol_decoder_jarolift_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + + switch(instance->decoder.parser_step) { + case JaroliftDecoderStepReset: + if((level) && DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta) { + instance->decoder.parser_step = JaroliftDecoderStepCheckPreambula; + instance->header_count++; + } + break; + case JaroliftDecoderStepCheckPreambula: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + instance->decoder.parser_step = JaroliftDecoderStepReset; + break; + } + if((!level) && (instance->header_count == 8) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_long * 5) < + subghz_protocol_jarolift_const.te_delta * 6)) { + // Found gap after header - 4000us +- 996us + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } else { + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + break; + case JaroliftDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = JaroliftDecoderStepCheckDuration; + } else { + instance->header_count = 0; + instance->decoder.parser_step = JaroliftDecoderStepReset; + } + break; + case JaroliftDecoderStepCheckDuration: + if(!level) { + if(instance->decoder.decode_count_bit == 64) { + instance->generic.data = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = JaroliftDecoderStepSaveDuration; + } else { + if(duration >= ((uint32_t)subghz_protocol_jarolift_const.te_long * 3)) { + // Add endbit + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_jarolift_const.te_long) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else if((DURATION_DIFF( + instance->decoder.te_last, + subghz_protocol_jarolift_const.te_short) < + subghz_protocol_jarolift_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if(instance->decoder.decode_count_bit == + subghz_protocol_jarolift_const.min_count_bit_for_found) { + instance->generic.data_2 = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + break; + } + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + } else { + instance->decoder.parser_step = JaroliftDecoderStepReset; + instance->header_count = 0; + } + break; + } +} + +/** + * Get button name. + * @param btn Button number, 4 bit + */ +static const char* subghz_protocol_jarolift_get_button_name(uint8_t btn) { + const char* btn_name; + switch(btn) { + case 0x1: + btn_name = "Learn"; + break; + case 0x2: + btn_name = "Down"; + break; + case 0x4: + btn_name = "Stop"; + break; + case 0x8: + btn_name = "Up"; + break; + default: + btn_name = "Unkn"; + break; + } + return btn_name; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + * @param data Input encrypted data + * @param keystore Pointer to a SubGhzKeystore* instance + */ +static void subghz_protocol_jarolift_remote_controller( + SubGhzBlockGeneric* instance, + SubGhzKeystore* keystore) { + // Jarolift Decoder + // 01.2026 - @xMasterX (MMX) & d82k & Steffen (@bastelbudenbuben de) + + // Key samples (reversed) + // 0x821EB600EAC2EAD4 - Btn Up - cnt 0059 group 0100 + // 0x821EB6007D9BD66A - Btn Up - cnt 005A group 0100 + // 0x821EB600A029FA0E - Btn Up - cnt 005B group 0100 + + uint32_t group = subghz_protocol_blocks_reverse_key(instance->data_2, 8); + uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, 64); + bool ret = false; + uint32_t decrypt = 0; + instance->serial = (key >> 32) & 0xFFFFFFF; + uint32_t hop = key & 0xFFFFFFFF; + + for + M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { + if(manufacture_code->type == KEELOQ_LEARNING_NORMAL_JAROLIFT) { + uint64_t man = subghz_protocol_keeloq_common_normal_learning( + instance->serial, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF)) { + ret = true; + } + break; + } + } + if(ret) { + instance->btn = (key >> 60) & 0xF; + instance->seed = ((decrypt >> 24) << 8) | (group >> 8); + instance->cnt = decrypt & 0xFFFF; + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(3); + } else { + instance->btn = 0; + instance->serial = 0; + instance->cnt = 0; + instance->seed = 0; + } +} + +uint32_t subghz_protocol_decoder_jarolift_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + uint32_t hash = 0; + uint32_t* p = (uint32_t*)&instance->generic.data; + for(size_t i = 0; i < 4; i++) { + hash ^= p[i]; + } + return hash; +} + +SubGhzProtocolStatus subghz_protocol_decoder_jarolift_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + uint8_t key_data[sizeof(uint64_t)] = {0}; + for(size_t i = 0; i < sizeof(uint64_t); i++) { + key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data_2 >> (i * 8)) & 0xFF; + } + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + } + + if((ret == SubGhzProtocolStatusOk) && + !flipper_format_insert_or_update_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + ret = SubGhzProtocolStatusErrorParserOthers; + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_jarolift_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->generic.data_2 = instance->generic.data_2 << 8 | key_data[i]; + } + } while(false); + return ret; +} + +static uint8_t subghz_protocol_jarolift_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x1: + btn = 0x2; + break; + case 0x2: + btn = 0x1; + break; + case 0x4: + btn = 0x1; + break; + case 0x8: + btn = 0x1; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) { + switch(original_btn_code) { + case 0x1: + btn = 0x4; + break; + case 0x2: + btn = 0x4; + break; + case 0x4: + btn = 0x2; + break; + case 0x8: + btn = 0x4; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) { + switch(original_btn_code) { + case 0x1: + btn = 0x8; + break; + case 0x2: + btn = 0x8; + break; + case 0x4: + btn = 0x8; + break; + case 0x8: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + +void subghz_protocol_decoder_jarolift_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + subghz_protocol_jarolift_remote_controller(&instance->generic, instance->keystore); + + // push protocol data to global variable + subghz_block_generic_global.cnt_is_available = true; + subghz_block_generic_global.cnt_length_bit = 16; + subghz_block_generic_global.current_cnt = instance->generic.cnt; + + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%0llX\r\n" + "Sn:%07lX Btn:%01X - %s\r\n" + "Cnt:%04lX Group:%04lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.data, + instance->generic.serial, + instance->generic.btn, + subghz_protocol_jarolift_get_button_name(instance->generic.btn), + instance->generic.cnt, + instance->generic.seed); +} diff --git a/lib/subghz/protocols/jarolift.h b/lib/subghz/protocols/jarolift.h new file mode 100644 index 000000000..f52ca15d7 --- /dev/null +++ b/lib/subghz/protocols/jarolift.h @@ -0,0 +1,108 @@ +#pragma once +#include "base.h" + +#define SUBGHZ_PROTOCOL_JAROLIFT_NAME "Jarolift" + +typedef struct SubGhzProtocolDecoderJarolift SubGhzProtocolDecoderJarolift; +typedef struct SubGhzProtocolEncoderJarolift SubGhzProtocolEncoderJarolift; + +extern const SubGhzProtocolDecoder subghz_protocol_jarolift_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_jarolift_encoder; +extern const SubGhzProtocol subghz_protocol_jarolift; + +/** + * Allocate SubGhzProtocolEncoderJarolift. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderJarolift* pointer to a SubGhzProtocolEncoderJarolift instance + */ +void* subghz_protocol_encoder_jarolift_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderJarolift. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + */ +void subghz_protocol_encoder_jarolift_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +SubGhzProtocolStatus + subghz_protocol_encoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + */ +void subghz_protocol_encoder_jarolift_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_jarolift_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderJarolift. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderJarolift* pointer to a SubGhzProtocolDecoderJarolift instance + */ +void* subghz_protocol_decoder_jarolift_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + */ +void subghz_protocol_decoder_jarolift_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + */ +void subghz_protocol_decoder_jarolift_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_jarolift_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_decoder_jarolift_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_jarolift_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderJarolift. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_jarolift_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderJarolift instance + * @param output Resulting text + */ +void subghz_protocol_decoder_jarolift_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index c69e11433..5212eacb3 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -15,10 +15,13 @@ #define TAG "SubGhzProtocolKeeloq" +//variable used to bypass CounterMode settings if user just change Counter or Button +static bool bypass = false; + static const SubGhzBlockConst subghz_protocol_keeloq_const = { .te_short = 400, .te_long = 800, - .te_delta = 140, + .te_delta = 180, .min_count_bit_for_found = 64, }; @@ -97,11 +100,19 @@ const SubGhzProtocol subghz_protocol_keeloq = { * @param keystore Pointer to a SubGhzKeystore* instance * @param manufacture_name */ -static void subghz_protocol_keeloq_check_remote_controller( +static uint32_t subghz_protocol_keeloq_check_remote_controller( SubGhzBlockGeneric* instance, SubGhzKeystore* keystore, const char** manufacture_name); +/** + * Defines the button value for the current btn_id + * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | + * @param last_btn_code Candidate for the last button + * @return Button code + */ +static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code); + void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment) { SubGhzProtocolEncoderKeeloq* instance = malloc(sizeof(SubGhzProtocolEncoderKeeloq)); @@ -109,8 +120,8 @@ void* subghz_protocol_encoder_keeloq_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); - instance->encoder.repeat = 100; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 1100; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -136,19 +147,69 @@ void subghz_protocol_encoder_keeloq_free(void* context) { static bool subghz_protocol_keeloq_gen_data( SubGhzProtocolEncoderKeeloq* instance, uint8_t btn, - bool counter_up) { + bool counter_up, + bool skip_btn_check) { + // No mf name set? -> set to "" + if(instance->manufacture_name == 0x0) { + instance->manufacture_name = ""; + } + // add gendata part + ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); + if(!skip_btn_check && (keeloq_counter_mode != 7)) { + // Save original button + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Prog mode checks and extra fixage of MF Names + if(prog_mode == PROG_MODE_KEELOQ_BFT) { + instance->manufacture_name = "BFT"; + } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { + instance->manufacture_name = "Aprimatic"; + } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { + instance->manufacture_name = "Dea_Mio"; + } + // Custom button (programming mode button) for BFT, Aprimatic, Dea_Mio + uint8_t klq_last_custom_btn = 0xA; + if((strcmp(instance->manufacture_name, "BFT") == 0) || + (strcmp(instance->manufacture_name, "Aprimatic") == 0) || + (strcmp(instance->manufacture_name, "Dea_Mio") == 0) || + (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0)) { + klq_last_custom_btn = 0xF; + } else if( + (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || + (strcmp(instance->manufacture_name, "Monarch") == 0) || + (strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || + (strcmp(instance->manufacture_name, "Genius_Bravo") == 0)) { + klq_last_custom_btn = 0xB; + } else if( + (strcmp(instance->manufacture_name, "Novoferm") == 0) || + (strcmp(instance->manufacture_name, "Stilmatic") == 0)) { + klq_last_custom_btn = 0x9; + } else if( + (strcmp(instance->manufacture_name, "EcoStar") == 0) || + (strcmp(instance->manufacture_name, "Sommer") == 0)) { + klq_last_custom_btn = 0x6; + } else if((strcmp(instance->manufacture_name, "AN-Motors") == 0)) { + klq_last_custom_btn = 0xC; + } else if((strcmp(instance->manufacture_name, "Cardin_S449") == 0)) { + klq_last_custom_btn = 0xD; + } + + btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn); + } + // end gendata part + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + uint32_t fix = (uint32_t)btn << 28 | instance->generic.serial; uint32_t hop = 0; uint64_t man = 0; uint64_t code_found_reverse; int res = 0; - // No mf name set? -> set to "" - if(instance->manufacture_name == 0x0) { - instance->manufacture_name = ""; - } // programming mode on / off conditions - ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(strcmp(instance->manufacture_name, "BFT") == 0) { // BFT programming mode on / off conditions if(btn == 0xF) { @@ -186,9 +247,10 @@ static bool subghz_protocol_keeloq_gen_data( if(counter_up && prog_mode == PROG_MODE_OFF) { // Counter increment conditions - if(keeloq_counter_mode == 0) { + if(keeloq_counter_mode == 0 || bypass) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF || bypass) { + bypass = false; // standart counter mode. PULL data from subghz_block_generic_global variables if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value @@ -257,8 +319,16 @@ static bool subghz_protocol_keeloq_gen_data( } else { instance->generic.cnt = 0xFFFF; } - } else { + } else if(keeloq_counter_mode == 6) { // Mode 6 - Freeze counter + } else { + // Mode 7 - Make 12 signals in row with mode 2 + // + 0x3333 each time + if((instance->generic.cnt + 0x3333) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += 0x3333; + } } } if(prog_mode == PROG_MODE_OFF) { @@ -299,7 +369,7 @@ static bool subghz_protocol_keeloq_gen_data( } else if( (strcmp(instance->manufacture_name, "DTM_Neo") == 0) || (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || - (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || + (strcmp(instance->manufacture_name, "Clemsa_Mutancode") == 0) || (strcmp(instance->manufacture_name, "Came_Space") == 0) || (strcmp(instance->manufacture_name, "Genius_Bravo") == 0) || (strcmp(instance->manufacture_name, "GSN") == 0) || @@ -307,19 +377,34 @@ static bool subghz_protocol_keeloq_gen_data( (strcmp(instance->manufacture_name, "Rossi") == 0) || (strcmp(instance->manufacture_name, "Pecinin") == 0) || (strcmp(instance->manufacture_name, "Steelmate") == 0) || - (strcmp(instance->manufacture_name, "Cardin_S449") == 0)) { + (strcmp(instance->manufacture_name, "Cardin_S449") == 0) || + (strcmp(instance->manufacture_name, "Stilmatic") == 0) || + (strcmp(instance->manufacture_name, "Wisniowski") == 0) || + (strcmp(instance->manufacture_name, "ATA_PTX4") == 0) || + (strcmp(instance->manufacture_name, "Fadini") == 0) || + (strcmp(instance->manufacture_name, "Seav") == 0)) { // DTM Neo, Came_Space uses 12bit serial -> simple learning - // FAAC_RC,XT , Mutanco_Mutancode, Genius_Bravo, GSN 12bit serial -> normal learning + // FAAC_RC,XT , Clemsa_Mutancode, Genius_Bravo, GSN 12bit serial -> normal learning // Rosh, Rossi, Pecinin -> 12bit serial - simple learning // Steelmate -> 12bit serial - normal learning // Cardin_S449 -> 12bit serial - normal learning + // Stilmatic (r-tech) -> 12bit serial - normal learning + // Wisniowski -> 12bit serial - normal learning + // ATA_PTX4 -> 12bit serial - normal learning + // Fadini -> 12bit serial - simple learning + // Seav -> 12bit serial - normal learning decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } else if( (strcmp(instance->manufacture_name, "NICE_Smilo") == 0) || (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0) || - (strcmp(instance->manufacture_name, "JCM_Tech") == 0)) { - // Nice Smilo, MHouse, JCM -> 8bit serial - simple learning + (strcmp(instance->manufacture_name, "JCM_Tech") == 0) || + (strcmp(instance->manufacture_name, "Pujol_Vario") == 0) || + (strcmp(instance->manufacture_name, "Pujol") == 0) || + (strcmp(instance->manufacture_name, "Erreka") == 0)) { + // Nice Smilo, MHouse, JCM, Pujol_Vario -> 8bit serial - simple learning + // Pujol -> 8bit serial - special learning + // Erreka -> 8bit serial - secure learning with seed decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; } else if( @@ -383,6 +468,28 @@ static bool subghz_protocol_keeloq_gen_data( fix, manufacture_code->key); hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); break; + case KEELOQ_LEARNING_AERF: + man = subghz_protocol_keeloq_common_learning_aerf( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_ERREKA: + man = subghz_protocol_keeloq_common_learning_erreka( + fix, instance->generic.seed, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_PUJOL: + man = subghz_protocol_keeloq_common_learning_pujol( + fix, manufacture_code->key); + hop = subghz_protocol_keeloq_common_encrypt(decrypt, man); + break; + case KEELOQ_LEARNING_SIMPLE_JCM: + //Simple Learning 8 bit serial + decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | + instance->generic.cnt; + hop = subghz_protocol_keeloq_common_encrypt( + decrypt, manufacture_code->key); + break; case KEELOQ_LEARNING_UNKNOWN: if(kl_type_en == 1) { hop = subghz_protocol_keeloq_common_encrypt( @@ -433,7 +540,7 @@ bool subghz_protocol_keeloq_create_data( instance->generic.cnt = cnt; instance->manufacture_name = manufacture_name; instance->generic.data_count_bit = 64; - if(subghz_protocol_keeloq_gen_data(instance, btn, false)) { + if(subghz_protocol_keeloq_gen_data(instance, btn, false, true)) { return ( subghz_block_generic_serialize(&instance->generic, flipper_format, preset) == SubGhzProtocolStatusOk); @@ -441,7 +548,7 @@ bool subghz_protocol_keeloq_create_data( return false; } -bool subghz_protocol_keeloq_bft_create_data( +bool subghz_protocol_keeloq_seed_create_data( void* context, FlipperFormat* flipper_format, uint32_t serial, @@ -459,7 +566,7 @@ bool subghz_protocol_keeloq_bft_create_data( instance->manufacture_name = manufacture_name; instance->generic.data_count_bit = 64; // hehehehe - if(subghz_protocol_keeloq_gen_data(instance, btn, false)) { + if(subghz_protocol_keeloq_gen_data(instance, btn, false, true)) { return ( subghz_block_generic_serialize(&instance->generic, flipper_format, preset) == SubGhzProtocolStatusOk); @@ -467,65 +574,15 @@ bool subghz_protocol_keeloq_bft_create_data( return false; } -/** - * Defines the button value for the current btn_id - * Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | - * @param last_btn_code Candidate for the last button - * @return Button code - */ -static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code); - -/** - * Generating an upload from data. - * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance - * @return true On success - */ -static bool - subghz_protocol_encoder_keeloq_get_upload(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { +static size_t subghz_protocol_encoder_keeloq_encode_to_timings( + SubGhzProtocolEncoderKeeloq* instance, + uint8_t btn, + bool counter_up, + size_t index) { furi_assert(instance); - - // Save original button - if(subghz_custom_btn_get_original() == 0) { - subghz_custom_btn_set_original(btn); - } - - // No mf name set? -> set to "" - if(instance->manufacture_name == 0x0) { - instance->manufacture_name = ""; - } - // Prog mode checks and extra fixage of MF Names - ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); - if(prog_mode == PROG_MODE_KEELOQ_BFT) { - instance->manufacture_name = "BFT"; - } else if(prog_mode == PROG_MODE_KEELOQ_APRIMATIC) { - instance->manufacture_name = "Aprimatic"; - } else if(prog_mode == PROG_MODE_KEELOQ_DEA_MIO) { - instance->manufacture_name = "Dea_Mio"; - } - // Custom button (programming mode button) for BFT, Aprimatic, Dea_Mio - uint8_t klq_last_custom_btn = 0xA; - if((strcmp(instance->manufacture_name, "BFT") == 0) || - (strcmp(instance->manufacture_name, "Aprimatic") == 0) || - (strcmp(instance->manufacture_name, "Dea_Mio") == 0) || - (strcmp(instance->manufacture_name, "NICE_MHOUSE") == 0)) { - klq_last_custom_btn = 0xF; - } else if( - (strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0) || - (strcmp(instance->manufacture_name, "Monarch") == 0) || - (strcmp(instance->manufacture_name, "NICE_Smilo") == 0)) { - klq_last_custom_btn = 0xB; - } else if( - (strcmp(instance->manufacture_name, "Novoferm") == 0) || - (strcmp(instance->manufacture_name, "Stilmatic") == 0)) { - klq_last_custom_btn = 0x9; - } else if( - (strcmp(instance->manufacture_name, "EcoStar") == 0) || - (strcmp(instance->manufacture_name, "Sommer") == 0)) { - klq_last_custom_btn = 0x6; - } else if((strcmp(instance->manufacture_name, "AN-Motors") == 0)) { - klq_last_custom_btn = 0xC; - } else if((strcmp(instance->manufacture_name, "Cardin_S449") == 0)) { - klq_last_custom_btn = 0xD; + // Generate new key + if(!subghz_protocol_keeloq_gen_data(instance, btn, counter_up, false)) { + return 0; } uint32_t gap_duration = subghz_protocol_keeloq_const.te_short * 40; @@ -533,24 +590,6 @@ static bool gap_duration = subghz_protocol_keeloq_const.te_short * 29; } - btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn); - - // Generate new key - if(subghz_protocol_keeloq_gen_data(instance, btn, true)) { - // OK - } else { - return false; - } - - size_t index = 0; - size_t size_upload = 11 * 2 + 2 + (instance->generic.data_count_bit * 2) + 4; - if(size_upload > instance->encoder.size_upload) { - FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); - return false; - } else { - instance->encoder.size_upload = size_upload; - } - //Send header for(uint8_t i = 11; i > 0; i--) { instance->encoder.upload[index++] = @@ -589,6 +628,58 @@ static bool level_duration_make(true, (uint32_t)subghz_protocol_keeloq_const.te_short); instance->encoder.upload[index++] = level_duration_make(false, gap_duration); + return index; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeeloq instance + * @return true On success + */ +static bool + subghz_protocol_encoder_keeloq_get_upload(SubGhzProtocolEncoderKeeloq* instance, uint8_t btn) { + furi_assert(instance); + + instance->encoder.size_upload = 0; + size_t upindex = 0; + + // if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file. + if(subghz_block_generic_global.cnt_need_override || + subghz_block_generic_global.btn_need_override) + bypass = true; + + // Create mode7 upload only if counter and button was not changed by SignalSettings menu + if(keeloq_counter_mode == 7 && !bypass) { + uint16_t temp_cnt = instance->generic.cnt; + instance->encoder.repeat = 1; + for(uint8_t i = 7; i > 0; i--) { + if(i == 3) { + instance->generic.cnt = 0x0000; + upindex = subghz_protocol_encoder_keeloq_encode_to_timings( + instance, (uint8_t)0x00, false, upindex); + continue; + } else if(i == 2) { + instance->generic.cnt = temp_cnt; + upindex = subghz_protocol_encoder_keeloq_encode_to_timings( + instance, btn, false, upindex); + continue; + } else if(i == 1) { + instance->generic.cnt = temp_cnt + 1; + upindex = subghz_protocol_encoder_keeloq_encode_to_timings( + instance, btn, false, upindex); + continue; + } + upindex = subghz_protocol_encoder_keeloq_encode_to_timings( + instance, (uint8_t)0x00, true, upindex); + } + instance->encoder.size_upload = upindex; + return true; + } else { + instance->encoder.repeat = 3; + instance->encoder.size_upload = + subghz_protocol_encoder_keeloq_encode_to_timings(instance, btn, true, upindex); + } + return true; } @@ -715,7 +806,7 @@ LevelDuration subghz_protocol_encoder_keeloq_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -749,6 +840,8 @@ void subghz_protocol_decoder_keeloq_reset(void* context) { // TODO instance->keystore->mfname = ""; instance->keystore->kl_type = 0; + // Reset seed? + instance->generic.seed = 0; } void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t duration) { @@ -901,9 +994,9 @@ static inline bool subghz_protocol_keeloq_check_decrypt_centurion( * @param hop Hop encrypted part of the parcel * @param keystore Pointer to a SubGhzKeystore* instance * @param manufacture_name - * @return true on successful search - */ -static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( + * @return uint32_t decrypt data */ + +static uint32_t subghz_protocol_keeloq_check_remote_controller_selector( SubGhzBlockGeneric* instance, uint32_t fix, uint32_t hop, @@ -926,7 +1019,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( const char* mfname = keystore->mfname; if(strcmp(mfname, "Unknown") == 0) { - return 1; + return 0; } else if(strcmp(mfname, "") == 0) { mf_not_set = true; } @@ -940,7 +1033,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } break; case KEELOQ_LEARNING_NORMAL: @@ -949,38 +1042,60 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( man = subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); - if(strcmp(furi_string_get_cstr(manufacture_code->name), "Centurion") == 0) { + if((strcmp(furi_string_get_cstr(manufacture_code->name), "Centurion") == 0)) { if(subghz_protocol_keeloq_check_decrypt_centurion(instance, decrypt, btn)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } } else { if(subghz_protocol_keeloq_check_decrypt( instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } } break; case KEELOQ_LEARNING_SECURE: bool reset_seed_back = false; if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { - if(instance->seed == 0) { - instance->seed = (fix & 0xFFFFFFF); - reset_seed_back = true; + // Try current seed from file if present + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; } + // Try seed from serial + //if(instance->seed == 0) { + instance->seed = (fix & 0xFFFFFFF); + reset_seed_back = true; + //} } + // Try seed from serial or zero seed man = subghz_protocol_keeloq_common_secure_learning( fix, instance->seed, manufacture_code->key); decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } else { if(reset_seed_back) instance->seed = 0; + // Try with zero seed (some strange remotes have been reported to use 0 seed) + man = subghz_protocol_keeloq_common_secure_learning( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt( + instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } } break; case KEELOQ_LEARNING_MAGIC_XOR_TYPE_1: @@ -990,7 +1105,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } break; case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1: @@ -1000,7 +1115,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } break; case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2: @@ -1010,7 +1125,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; } break; case KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3: @@ -1020,7 +1135,56 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; - return 1; + return decrypt; + } + break; + case KEELOQ_LEARNING_AERF: + man = subghz_protocol_keeloq_common_learning_aerf(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt_derived(hop, man, 0x240u); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } + decrypt = subghz_protocol_keeloq_common_decrypt_derived(hop, man, 0x210u); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } + break; + case KEELOQ_LEARNING_ERREKA: + man = subghz_protocol_keeloq_common_learning_erreka( + fix, instance->seed, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } + break; + case KEELOQ_LEARNING_PUJOL: + man = subghz_protocol_keeloq_common_learning_pujol(fix, manufacture_code->key); + decrypt = subghz_protocol_keeloq_common_decrypt(hop, man); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; + } + break; + case KEELOQ_LEARNING_SIMPLE_JCM: + // Simple Learning 8 bit serial + decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); + if(subghz_protocol_keeloq_check_decrypt(instance, decrypt, btn, end_serial)) { + *manufacture_name = furi_string_get_cstr(manufacture_code->name); + keystore->mfname = *manufacture_name; + return decrypt; } break; case KEELOQ_LEARNING_UNKNOWN: @@ -1030,7 +1194,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 1; - return 1; + return decrypt; } // Check for mirrored man @@ -1046,7 +1210,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 1; - return 1; + return decrypt; } //########################### @@ -1059,7 +1223,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 2; - return 1; + return decrypt; } // Check for mirrored man @@ -1069,7 +1233,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 2; - return 1; + return decrypt; } // Secure Learning @@ -1080,7 +1244,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 3; - return 1; + return decrypt; } // Check for mirrored man @@ -1091,7 +1255,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 3; - return 1; + return decrypt; } // Magic xor type1 learning @@ -1102,7 +1266,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 4; - return 1; + return decrypt; } // Check for mirrored man @@ -1112,7 +1276,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( *manufacture_name = furi_string_get_cstr(manufacture_code->name); keystore->mfname = *manufacture_name; keystore->kl_type = 4; - return 1; + return decrypt; } break; @@ -1128,7 +1292,7 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( return 0; } -static void subghz_protocol_keeloq_check_remote_controller( +static uint32_t subghz_protocol_keeloq_check_remote_controller( SubGhzBlockGeneric* instance, SubGhzKeystore* keystore, const char** manufacture_name) { @@ -1137,6 +1301,7 @@ static void subghz_protocol_keeloq_check_remote_controller( uint32_t key_fix = key >> 32; uint32_t key_hop = key & 0x00000000ffffffff; static uint16_t temp_counter = 0; // Be careful with prog_mode + uint32_t resdecrypt = 0; // If we are in BFT / Aprimatic / Dea_Mio programming mode we will set previous remembered counter and skip mf keys check ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); @@ -1161,7 +1326,7 @@ static void subghz_protocol_keeloq_check_remote_controller( keystore->mfname = *manufacture_name; instance->cnt = key_hop >> 16; } else { - subghz_protocol_keeloq_check_remote_controller_selector( + resdecrypt = subghz_protocol_keeloq_check_remote_controller_selector( instance, key_fix, key_hop, keystore, manufacture_name); } } else { @@ -1178,7 +1343,7 @@ static void subghz_protocol_keeloq_check_remote_controller( instance->cnt = key_hop >> 16; } else { // Else we have mfname that is not AN-Motors or HCS101 we should check it via default selector - subghz_protocol_keeloq_check_remote_controller_selector( + resdecrypt = subghz_protocol_keeloq_check_remote_controller_selector( instance, key_fix, key_hop, keystore, manufacture_name); } } @@ -1215,6 +1380,8 @@ static void subghz_protocol_keeloq_check_remote_controller( } // Set max custom buttons subghz_custom_btn_set_max(4); + + return resdecrypt; } uint32_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { @@ -1485,7 +1652,10 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; - subghz_protocol_keeloq_check_remote_controller( + uint32_t hopdecrypt = 0; + // Try to get decrypt for display if mf is known, if not it will be 0 and display will be without decrypt part since it might come already decrypted like in HCS101 or AN-Motors + // Or we might have Unknown MF + hopdecrypt = subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); uint32_t code_found_hi = instance->generic.data >> 32; @@ -1496,7 +1666,32 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; - if(strcmp(instance->manufacture_name, "BFT") == 0) { + // Allow button edit + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + + if(strcmp(instance->manufacture_name, "AN-Motors") == 0) { + // No counter only pseudo counter + subghz_block_generic_global.cnt_is_available = false; + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX PsCn:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" + "MF:%s", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name); + } else if(strcmp(instance->manufacture_name, "HCS101") == 0) { + // Counter is present but not encrypted subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; @@ -1506,7 +1701,7 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output "Key:%08lX%08lX\r\n" "Fix:0x%08lX Cnt:%04lX\r\n" "Hop:0x%08lX Btn:%01X\r\n" - "MF:%s Sd:%08lX", + "MF:%s", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_hi, @@ -1515,10 +1710,57 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output instance->generic.cnt, code_found_reverse_lo, instance->generic.btn, - instance->manufacture_name, - instance->generic.seed); + instance->manufacture_name); + } else if( + (strcmp(instance->manufacture_name, "BFT") == 0) || + (strcmp(instance->manufacture_name, "Erreka") == 0)) { + // Allow counter edit + subghz_block_generic_global.cnt_is_available = true; + subghz_block_generic_global.cnt_length_bit = 16; + subghz_block_generic_global.current_cnt = instance->generic.cnt; + + ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); + if(prog_mode == PROG_MODE_KEELOQ_BFT) { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" + "MF:%s PRG Sd:%08lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + code_found_reverse_lo, + instance->generic.btn, + instance->manufacture_name, + instance->generic.seed); + } else { + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Key:%08lX%08lX\r\n" + "Fix:0x%08lX Cnt:%04lX\r\n" + "Hop:0x%08lX Btn:%01X\r\n" + "MF:%s Sd:%08lX", + instance->generic.protocol_name, + instance->generic.data_count_bit, + code_found_hi, + code_found_lo, + code_found_reverse_hi, + instance->generic.cnt, + hopdecrypt, + instance->generic.btn, + instance->manufacture_name, + instance->generic.seed); + } } else if(strcmp(instance->manufacture_name, "Unknown") == 0) { + // No counter info with unknown MF instance->generic.cnt = 0x0; + subghz_block_generic_global.cnt_is_available = false; furi_string_cat_printf( output, "%s %dbit\r\n" @@ -1535,6 +1777,7 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output instance->generic.btn, instance->manufacture_name); } else { + // All other known MF with counter info, allow counter edit subghz_block_generic_global.cnt_is_available = true; subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; @@ -1551,7 +1794,7 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output code_found_lo, code_found_reverse_hi, instance->generic.cnt, - code_found_reverse_lo, + hopdecrypt, instance->generic.btn, instance->manufacture_name); } diff --git a/lib/subghz/protocols/keeloq_common.c b/lib/subghz/protocols/keeloq_common.c index a9a2238dd..2486ec45f 100644 --- a/lib/subghz/protocols/keeloq_common.c +++ b/lib/subghz/protocols/keeloq_common.c @@ -140,3 +140,97 @@ inline uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) { return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF); } + +// Key utils + +static inline uint32_t subghz_protocol_keeloq_common_manufacturer_nl_extend( + uint32_t x, + uint32_t k_lo, + uint32_t k_hi, + uint32_t outer_limit) { + uint32_t r4 = outer_limit; + uint32_t r5 = 0u; + const uint32_t r6 = KEELOQ_NLF; + + while(r5 != r4) { + if(r5 < 0x210u) { + uint32_t r1 = (x >> 15) & 1u; + uint32_t r7 = r1 ^ ((x >> 1) | (x << 31)); + r1 = (15u - r5) & 0x3Fu; + uint32_t lr = 32u - r1; + uint32_t ip = r1 - 32u; + lr = k_hi << lr; + r1 = k_lo >> r1; + ip = (r1 < 32u) ? (k_hi >> ip) : 0u; + r1 = (r1 | lr | ip) & 1u; + ip = (x >> 30) & 1u; + r1 ^= r7; + r7 = (x >> 25) & 1u; + r7 += ip << 1; + ip = (x >> 19) & 1u; + ip += r7 << 1; + r7 = (x >> 8) & 1u; + r7 += ip << 1; + x &= 1u; + x += r7 << 1; + x = (int32_t)r6 >> (x & 31u); + x &= 1u; + x ^= r1; + } + r5 += 1u; + } + return x; +} + +static inline uint32_t subghz_protocol_keeloq_common_word_rotate16(uint32_t v) { + return (v >> 16) | (v << 16); +} + +inline uint32_t subghz_protocol_keeloq_common_decrypt_derived( + uint32_t hop_encrypted, + uint64_t derived_manufacturing_key, + uint32_t outer_limit) { + return subghz_protocol_keeloq_common_manufacturer_nl_extend( + hop_encrypted, + (uint32_t)derived_manufacturing_key, + (uint32_t)(derived_manufacturing_key >> 32u), + outer_limit); +} + +// Protocol (Manufacturer) specific learning +// TODO: Better documentation for these functions + +inline uint64_t subghz_protocol_keeloq_common_learning_aerf(uint32_t data, const uint64_t key) { + uint32_t k_lo = (uint32_t)key; + uint32_t k_hi = (uint32_t)(key >> 32); + uint32_t d = data & 0x0FFFFFFFu; + uint32_t x = d | 0x20000000u; + x = subghz_protocol_keeloq_common_manufacturer_nl_extend(x, k_lo, k_hi, 0x40u); + uint32_t k1 = x; + x = d | 0x60000000u; + x = subghz_protocol_keeloq_common_manufacturer_nl_extend(x, k_lo, k_hi, 0x40u); + return ((uint64_t)x << 32) | k1; +} + +inline uint64_t + subghz_protocol_keeloq_common_learning_erreka(uint32_t data, uint32_t mix, const uint64_t key) { + uint32_t d = data & 0x0FFFFFFFu; + uint32_t k1 = subghz_protocol_keeloq_common_decrypt(d | 0x20000000u, key); + uint32_t r4 = mix >> 4; + uint32_t r1 = (mix << 4) & 0xF000F000u; + r4 = (r4 & 0x0F000F00u) | r1; + uint32_t r5 = mix & 0x00FF00FFu; + uint32_t x = r4 | r5; + x |= 0x60000000u; + uint32_t k2 = subghz_protocol_keeloq_common_decrypt(x, key); + return ((uint64_t)k2 << 32) | k1; +} + +inline uint64_t subghz_protocol_keeloq_common_learning_pujol(uint32_t data, const uint64_t key) { + uint32_t d = data & 0x0FFFFFFFu; + uint32_t w1 = subghz_protocol_keeloq_common_decrypt(d | 0x20000000u, key); + uint32_t w2 = subghz_protocol_keeloq_common_decrypt(d | 0x60000000u, key); + uint32_t k1 = subghz_protocol_keeloq_common_word_rotate16(w1); + uint32_t k2 = subghz_protocol_keeloq_common_word_rotate16(w2); + return ((uint64_t)k2 << 32) | k1; +} diff --git a/lib/subghz/protocols/keeloq_common.h b/lib/subghz/protocols/keeloq_common.h index 90c5ef6d7..93904f194 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -26,6 +26,12 @@ #define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 7u #define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 8u // #define BENINCA_ARC_KEY_TYPE 9u -- RESERVED +#define KEELOQ_LEARNING_SIMPLE_KINGGATES 10u +#define KEELOQ_LEARNING_NORMAL_JAROLIFT 11u +#define KEELOQ_LEARNING_ERREKA 12u +#define KEELOQ_LEARNING_PUJOL 13u +#define KEELOQ_LEARNING_AERF 14u +#define KEELOQ_LEARNING_SIMPLE_JCM 15u /** * Simple Learning Encrypt @@ -99,3 +105,19 @@ uint64_t subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data */ uint64_t subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man); + +// Protocol (Manufacturer) specific learning +// TODO: Better documentation for these functions + +uint64_t subghz_protocol_keeloq_common_learning_aerf(uint32_t data, const uint64_t key); + +uint64_t + subghz_protocol_keeloq_common_learning_erreka(uint32_t data, uint32_t mix, const uint64_t key); + +uint64_t subghz_protocol_keeloq_common_learning_pujol(uint32_t data, const uint64_t key); + +// Utils +uint32_t subghz_protocol_keeloq_common_decrypt_derived( + uint32_t hop_encrypted, + uint64_t derived_manufacturing_key, + uint32_t outer_limit); diff --git a/lib/subghz/protocols/keyfinder.c b/lib/subghz/protocols/keyfinder.c new file mode 100644 index 000000000..beb8f0882 --- /dev/null +++ b/lib/subghz/protocols/keyfinder.c @@ -0,0 +1,363 @@ +#include "keyfinder.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolKeyFinder" + +static const SubGhzBlockConst subghz_protocol_keyfinder_const = { + .te_short = 400, + .te_long = 1200, + .te_delta = 150, + .min_count_bit_for_found = 24, +}; + +struct SubGhzProtocolDecoderKeyFinder { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + uint8_t end_count; +}; + +struct SubGhzProtocolEncoderKeyFinder { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + KeyFinderDecoderStepReset = 0, + KeyFinderDecoderStepSaveDuration, + KeyFinderDecoderStepCheckDuration, + KeyFinderDecoderStepFinish, +} KeyFinderDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_keyfinder_decoder = { + .alloc = subghz_protocol_decoder_keyfinder_alloc, + .free = subghz_protocol_decoder_keyfinder_free, + + .feed = subghz_protocol_decoder_keyfinder_feed, + .reset = subghz_protocol_decoder_keyfinder_reset, + + .get_hash_data = subghz_protocol_decoder_keyfinder_get_hash_data, + .serialize = subghz_protocol_decoder_keyfinder_serialize, + .deserialize = subghz_protocol_decoder_keyfinder_deserialize, + .get_string = subghz_protocol_decoder_keyfinder_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_keyfinder_encoder = { + .alloc = subghz_protocol_encoder_keyfinder_alloc, + .free = subghz_protocol_encoder_keyfinder_free, + + .deserialize = subghz_protocol_encoder_keyfinder_deserialize, + .stop = subghz_protocol_encoder_keyfinder_stop, + .yield = subghz_protocol_encoder_keyfinder_yield, +}; + +const SubGhzProtocol subghz_protocol_keyfinder = { + .name = SUBGHZ_PROTOCOL_KEYFINDER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_keyfinder_decoder, + .encoder = &subghz_protocol_keyfinder_encoder, +}; + +void* subghz_protocol_encoder_keyfinder_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderKeyFinder* instance = malloc(sizeof(SubGhzProtocolEncoderKeyFinder)); + + instance->base.protocol = &subghz_protocol_keyfinder; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 5; + instance->encoder.size_upload = 60; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_keyfinder_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderKeyFinder* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +static void + subghz_protocol_encoder_keyfinder_get_upload(SubGhzProtocolEncoderKeyFinder* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key data 24 bit first + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_long); + + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short); + } + } + // End bits (3 times then 1 more with gap 4k us) + for(uint8_t i = 0; i < 3; i++) { + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short); + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_keyfinder_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_keyfinder_const.te_short * 10); + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_keyfinder_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = instance->data >> 4; + instance->btn = instance->data & 0xF; +} + +SubGhzProtocolStatus + subghz_protocol_encoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderKeyFinder* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_keyfinder_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_keyfinder_check_remote_controller(&instance->generic); + subghz_protocol_encoder_keyfinder_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_keyfinder_stop(void* context) { + SubGhzProtocolEncoderKeyFinder* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_keyfinder_yield(void* context) { + SubGhzProtocolEncoderKeyFinder* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_keyfinder_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderKeyFinder* instance = malloc(sizeof(SubGhzProtocolDecoderKeyFinder)); + instance->base.protocol = &subghz_protocol_keyfinder; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_keyfinder_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + free(instance); +} + +void subghz_protocol_decoder_keyfinder_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + instance->decoder.parser_step = KeyFinderDecoderStepReset; +} + +void subghz_protocol_decoder_keyfinder_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + + // KeyFinder Decoder + // 2026.03 - @xMasterX (MMX) + + // Key samples + // + // 433.92 MHz AM650 + // Serial ID Serial ID + // RED - C396F E = 11000011100101101111 1110 + // PURPLE - C396F B = 11000011100101101111 1011 + // GREEN - C396F D = 11000011100101101111 1101 + // BLUE - C396F C = 11000011100101101111 1100 + + switch(instance->decoder.parser_step) { + case KeyFinderDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short * 10) < + subghz_protocol_keyfinder_const.te_delta * 5)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + } + break; + case KeyFinderDecoderStepSaveDuration: + if(instance->decoder.decode_count_bit == + subghz_protocol_keyfinder_const.min_count_bit_for_found) { + if((level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + instance->end_count++; + if(instance->end_count == 4) { + instance->decoder.parser_step = KeyFinderDecoderStepFinish; + instance->end_count = 0; + } + } else if( + (!level) && (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + break; + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->end_count = 0; + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + } + + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KeyFinderDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + case KeyFinderDecoderStepCheckDuration: + if(!level) { + // Bit 1 is short and long timing = 400us HIGH (te_last) and 1200us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_long) < + subghz_protocol_keyfinder_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + // Bit 0 is long and short timing = 1200us HIGH (te_last) and 400us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_keyfinder_const.te_long) < + subghz_protocol_keyfinder_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_keyfinder_const.te_short) < + subghz_protocol_keyfinder_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KeyFinderDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + } else { + instance->decoder.parser_step = KeyFinderDecoderStepReset; + } + break; + case KeyFinderDecoderStepFinish: + // If got 24 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_keyfinder_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->end_count = 0; + instance->decoder.parser_step = KeyFinderDecoderStepReset; + break; + } +} + +uint8_t subghz_protocol_decoder_keyfinder_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_keyfinder_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_keyfinder_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_keyfinder_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderKeyFinder* instance = context; + + subghz_protocol_keyfinder_check_remote_controller(&instance->generic); + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + // for future use + // // push protocol data to global variable + // subghz_block_generic_global.btn_is_available = false; + // subghz_block_generic_global.current_btn = instance->generic.btn; + // subghz_block_generic_global.btn_length_bit = 4; + // // + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%06lX\r\n" + "Yek: 0x%06lX\r\n" + "Serial: 0x%05lX\r\n" + "ID: 0x%0X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFF), + (uint32_t)(code_found_reverse & 0xFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/keyfinder.h b/lib/subghz/protocols/keyfinder.h new file mode 100644 index 000000000..d37f24df4 --- /dev/null +++ b/lib/subghz/protocols/keyfinder.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_KEYFINDER_NAME "KeyFinder" + +typedef struct SubGhzProtocolDecoderKeyFinder SubGhzProtocolDecoderKeyFinder; +typedef struct SubGhzProtocolEncoderKeyFinder SubGhzProtocolEncoderKeyFinder; + +extern const SubGhzProtocolDecoder subghz_protocol_keyfinder_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_keyfinder_encoder; +extern const SubGhzProtocol subghz_protocol_keyfinder; + +/** + * Allocate SubGhzProtocolEncoderKeyFinder. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderKeyFinder* pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void* subghz_protocol_encoder_keyfinder_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderKeyFinder. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void subghz_protocol_encoder_keyfinder_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + */ +void subghz_protocol_encoder_keyfinder_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderKeyFinder instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_keyfinder_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderKeyFinder. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderKeyFinder* pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void* subghz_protocol_decoder_keyfinder_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void subghz_protocol_decoder_keyfinder_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + */ +void subghz_protocol_decoder_keyfinder_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_keyfinder_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_keyfinder_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_keyfinder_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderKeyFinder. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_keyfinder_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderKeyFinder instance + * @param output Resulting text + */ +void subghz_protocol_decoder_keyfinder_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index e15e7d371..1fad835e9 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -103,7 +103,7 @@ void* subghz_protocol_encoder_kinggates_stylo_4k_alloc(SubGhzEnvironment* enviro instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -134,7 +134,7 @@ LevelDuration subghz_protocol_encoder_kinggates_stylo_4k_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -156,6 +156,10 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( btn = subghz_protocol_kinggates_stylo_4k_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + // Check for OFEX (overflow experimental) mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { // standart counter mode. PULL data from subghz_block_generic_global variables @@ -189,7 +193,7 @@ static bool subghz_protocol_kinggates_stylo_4k_gen_data( uint64_t encrypt = 0; for M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - if(strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k") == 0) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { // Simple Learning encrypt = subghz_protocol_keeloq_common_encrypt(hop, manufacture_code->key); encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); @@ -222,7 +226,7 @@ bool subghz_protocol_kinggates_stylo_4k_create_data( uint64_t encrypt = 0; for M_EACH(manufacture_code, *subghz_keystore_get_data(instance->keystore), SubGhzKeyArray_t) { - if(strcmp(furi_string_get_cstr(manufacture_code->name), "Kingates_Stylo4k") == 0) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { // Simple Learning encrypt = subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key); encrypt = subghz_protocol_blocks_reverse_key(encrypt, 32); @@ -458,7 +462,6 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, subghz_protocol_kinggates_stylo_4k_const.te_delta * 2) { instance->decoder.parser_step = KingGates_stylo_4kDecoderStepSaveDuration; instance->decoder.decode_data = 0; - instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; } @@ -478,7 +481,6 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, instance->decoder.parser_step = KingGates_stylo_4kDecoderStepReset; instance->decoder.decode_data = 0; - instance->generic.data_2 = 0; instance->decoder.decode_count_bit = 0; instance->header_count = 0; break; @@ -563,13 +565,13 @@ static void subghz_protocol_kinggates_stylo_4k_remote_controller( for M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) { - if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE) { + if(manufacture_code->type == KEELOQ_LEARNING_SIMPLE_KINGGATES) { decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); if(((decrypt >> 28) == instance->btn) && (((decrypt >> 24) & 0x0F) == 0x0C) && (((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF))) { ret = true; - break; } + break; } } if(ret) { @@ -731,6 +733,11 @@ void subghz_protocol_decoder_kinggates_stylo_4k_get_string(void* context, FuriSt subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s\r\n" diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 3da43b3ea..856cad9dc 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -84,9 +84,8 @@ void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_legrand; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = - (subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2; + instance->encoder.repeat = 3; + instance->encoder.size_upload = subghz_protocol_legrand_const.min_count_bit_for_found * 2 + 1; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -107,50 +106,31 @@ void subghz_protocol_encoder_legrand_free(void* context) { static bool subghz_protocol_encoder_legrand_get_upload(SubGhzProtocolEncoderLegrand* instance) { furi_assert(instance); - //size_t size_upload = (instance->generic.data_count_bit * 2) + 1; - //if(size_upload != instance->encoder.size_upload) { - // FURI_LOG_E(TAG, "Invalid data bit count"); - // return false; - //} + size_t size_upload = (instance->generic.data_count_bit * 2) + 1; + if(size_upload != instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Invalid data bit count"); + return false; + } size_t index = 0; - for(size_t r = 0; r < 5; r++) { - // Send sync - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)instance->te * 16); // 5728 - // Send key data - for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { - if(bit_read(instance->generic.data, i - 1)) { - // send bit 1 - if(i == instance->generic.data_count_bit) { - //Send first bit - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)instance->te * 3); - } else { - // send bit 1 regular - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)instance->te); - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)instance->te * 3); - } - } else { - // send bit 0 - if(i == instance->generic.data_count_bit) { - //Send first bit - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)instance->te); - } else { - // send bit 0 regular - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)instance->te * 3); - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)instance->te); - } - } + // Send sync + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te * 16); + + // Send key data + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // send bit 1 + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)instance->te); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)instance->te * 3); + } else { + // send bit 0 + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)instance->te * 3); + instance->encoder.upload[index++] = level_duration_make(true, (uint32_t)instance->te); } } - instance->encoder.size_upload = index; return true; } @@ -208,7 +188,7 @@ LevelDuration subghz_protocol_encoder_legrand_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -243,7 +223,7 @@ void subghz_protocol_decoder_legrand_feed(void* context, bool level, uint32_t du switch(instance->decoder.parser_step) { case LegrandDecoderStepReset: if(!level && DURATION_DIFF(duration, subghz_protocol_legrand_const.te_short * 16) < - subghz_protocol_legrand_const.te_delta * 8) { // 6000 +- 1200 + subghz_protocol_legrand_const.te_delta * 8) { instance->decoder.parser_step = LegrandDecoderStepFirstBit; instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 628ec9914..c35f02de1 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -83,7 +83,7 @@ void* subghz_protocol_encoder_linear_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_linear; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 28; //max 10bit*2 + 2 (start, stop) instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -192,7 +192,7 @@ LevelDuration subghz_protocol_encoder_linear_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index 6efa33009..59df83c16 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -85,7 +85,7 @@ void* subghz_protocol_encoder_linear_delta3_alloc(SubGhzEnvironment* environment instance->base.protocol = &subghz_protocol_linear_delta3; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 16; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -196,7 +196,7 @@ LevelDuration subghz_protocol_encoder_linear_delta3_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 66d7b20a0..53c3641eb 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -81,7 +81,7 @@ void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_magellan; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -199,7 +199,7 @@ LevelDuration subghz_protocol_encoder_magellan_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -502,6 +502,13 @@ void subghz_protocol_decoder_magellan_get_string(void* context, FuriString* outp furi_assert(context); SubGhzProtocolDecoderMagellan* instance = context; subghz_protocol_magellan_check_remote_controller(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index 0064e398e..a50f01504 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -79,7 +79,7 @@ void* subghz_protocol_encoder_marantec_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_marantec; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -245,7 +245,7 @@ LevelDuration subghz_protocol_encoder_marantec_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -395,6 +395,12 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); bool crc_ok = (crc == (instance->generic.data & 0xFF)); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 3d4679afb..6b77d0511 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -107,11 +107,3 @@ SubGhzProtocolStatus * @param output Resulting text */ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* output); - -/** - * Calculate CRC8 for Marantec protocol. - * @param data Pointer to the data buffer - * @param len Length of the data buffer - * @return CRC8 value - */ -uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len); diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index a5eed4ed9..94120ad3b 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -75,8 +75,8 @@ void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_marantec24; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 512; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -97,40 +97,36 @@ static void subghz_protocol_encoder_marantec24_get_upload(SubGhzProtocolEncoderMarantec24* instance) { furi_assert(instance); size_t index = 0; - // Send initial GAP to trigger decoder - instance->encoder.upload[index++] = - level_duration_make(false, (uint32_t)subghz_protocol_marantec24_const.te_long * 9); - for(size_t r = 0; r < 4; r++) { - // Send key and GAP - for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { - if(bit_read(instance->generic.data, i - 1)) { - // Send bit 1 - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short); - if(i == 1) { - //Send gap if bit was last - instance->encoder.upload[index++] = level_duration_make( - false, - (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + - subghz_protocol_marantec24_const.te_short); - } else { - instance->encoder.upload[index++] = level_duration_make( - false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2); - } + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + + subghz_protocol_marantec24_const.te_short); } else { - // Send bit 0 - instance->encoder.upload[index++] = - level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long); - if(i == 1) { - //Send gap if bit was last - instance->encoder.upload[index++] = level_duration_make( - false, - (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + - subghz_protocol_marantec24_const.te_short); // 15200 - } else { - instance->encoder.upload[index++] = level_duration_make( - false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3); - } + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_marantec24_const.te_long * 2); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_marantec24_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, + (uint32_t)subghz_protocol_marantec24_const.te_long * 9 + + subghz_protocol_marantec24_const.te_short); + } else { + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_marantec24_const.te_short * 3); } } } @@ -167,7 +163,6 @@ SubGhzProtocolStatus subghz_protocol_marantec24_check_remote_controller(&instance->generic); subghz_protocol_encoder_marantec24_get_upload(instance); - instance->encoder.front = 0; // reset before start instance->encoder.is_running = true; } while(false); @@ -177,7 +172,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_marantec24_stop(void* context) { SubGhzProtocolEncoderMarantec24* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; // reset position } LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) { @@ -191,7 +185,7 @@ LevelDuration subghz_protocol_encoder_marantec24_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -239,7 +233,7 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile switch(instance->decoder.parser_step) { case Marantec24DecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 6)) { + subghz_protocol_marantec24_const.te_delta * 4)) { //Found GAP instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -275,7 +269,7 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile } else if( // End of the key DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 6) { + subghz_protocol_marantec24_const.te_delta * 4) { //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_marantec24_const.te_long) < @@ -340,6 +334,12 @@ void subghz_protocol_decoder_marantec24_get_string(void* context, FuriString* ou subghz_protocol_marantec24_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c index 354db4936..841744612 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -91,7 +91,7 @@ void* subghz_protocol_encoder_mastercode_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_mastercode; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 72; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -201,7 +201,7 @@ LevelDuration subghz_protocol_encoder_mastercode_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -345,10 +345,17 @@ void subghz_protocol_decoder_mastercode_get_string(void* context, FuriString* ou furi_assert(context); SubGhzProtocolDecoderMastercode* instance = context; subghz_protocol_mastercode_check_remote_controller(&instance->generic); + + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:%llX Btn %X\r\n" + "Key:%llX Btn:%X\r\n" " +: " DIP_PATTERN "\r\n" " o: " DIP_PATTERN "\r\n" " -: " DIP_PATTERN "\r\n", diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 144a4147a..b8d36126d 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -89,7 +89,7 @@ void* subghz_protocol_encoder_megacode_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_megacode; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -220,7 +220,7 @@ LevelDuration subghz_protocol_encoder_megacode_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -407,6 +407,12 @@ void subghz_protocol_decoder_megacode_get_string(void* context, FuriString* outp SubGhzProtocolDecoderMegaCode* instance = context; subghz_protocol_megacode_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 3; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 813586952..42a3e7d06 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -79,7 +79,7 @@ void* subghz_protocol_encoder_nero_radio_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_nero_radio; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -212,7 +212,7 @@ LevelDuration subghz_protocol_encoder_nero_radio_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -432,6 +432,12 @@ void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* ou subghz_protocol_nero_radio_parse_data(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 3616a6207..587496589 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -78,7 +78,7 @@ void* subghz_protocol_encoder_nero_sketch_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_nero_sketch; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -193,7 +193,7 @@ LevelDuration subghz_protocol_encoder_nero_sketch_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 49a49ceae..0d7ab9656 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -10,7 +10,7 @@ static const SubGhzBlockConst subghz_protocol_nice_flo_const = { .te_short = 700, .te_long = 1400, - .te_delta = 200, + .te_delta = 250, .min_count_bit_for_found = 12, }; @@ -77,7 +77,7 @@ void* subghz_protocol_encoder_nice_flo_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_nice_flo; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -179,7 +179,7 @@ LevelDuration subghz_protocol_encoder_nice_flo_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -213,8 +213,8 @@ void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t d switch(instance->decoder.parser_step) { case NiceFloDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_nice_flo_const.te_short * 36) < - subghz_protocol_nice_flo_const.te_delta * 36)) { - //Found header Nice Flo + subghz_protocol_nice_flo_const.te_delta * 29)) { + //Found header Nice Flo (25200us +- 7250us) instance->decoder.parser_step = NiceFloDecoderStepFoundStartBit; } break; @@ -328,8 +328,8 @@ void subghz_protocol_decoder_nice_flo_get_string(void* context, FuriString* outp furi_string_cat_printf( output, "%s %dbit\r\n" - "Key:0x%08lX\r\n" - "Yek:0x%08lX\r\n", + "Key:0x%06lX\r\n" + "Yek:0x%06lX\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_lo, diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 48d413eac..2d67caae8 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -21,6 +21,9 @@ #define SUBGHZ_NICE_FLOR_S_RAINBOW_TABLE_SIZE_BYTES 32 #define SUBGHZ_NO_NICE_FLOR_S_RAINBOW_TABLE 0 +//variable used to bypass CounterMode settings if user just change Counter or Button +static bool bypass = false; + static const SubGhzBlockConst subghz_protocol_nice_flor_s_const = { .te_short = 500, .te_long = 1000, @@ -108,8 +111,8 @@ void* subghz_protocol_encoder_nice_flor_s_alloc(SubGhzEnvironment* environment) FURI_LOG_D( TAG, "Loading rainbow table from %s", instance->nice_flor_s_rainbow_table_file_name); } - instance->encoder.repeat = 10; - instance->encoder.size_upload = 2400; //wrong!! upload 186*16 = 2976 - actual size about 1728 + instance->encoder.repeat = 1; + instance->encoder.size_upload = 2400; // 2368 for Nice ONE instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -151,15 +154,26 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( btn = subghz_protocol_nice_flor_s_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) { + bypass = true; + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + } + size_t size_upload = ((instance->generic.data_count_bit * 2) + ((37 + 2 + 2) * 2) * 16); if(size_upload > instance->encoder.size_upload) { FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); } else { instance->encoder.size_upload = size_upload; } - if(nice_flors_counter_mode == 0) { + + // if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file. + if(subghz_block_generic_global.cnt_need_override) bypass = true; + + if(nice_flors_counter_mode == 0 || bypass) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF || bypass) { + bypass = false; // standart counter mode. PULL data from subghz_block_generic_global variables if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value @@ -353,7 +367,7 @@ LevelDuration subghz_protocol_encoder_nice_flor_s_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -941,6 +955,11 @@ void subghz_protocol_decoder_nice_flor_s_get_string(void* context, FuriString* o subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + if(instance->generic.data_count_bit == NICE_ONE_COUNT_BIT) { furi_string_cat_printf( output, diff --git a/lib/subghz/protocols/nord_ice.c b/lib/subghz/protocols/nord_ice.c new file mode 100644 index 000000000..858e1ce23 --- /dev/null +++ b/lib/subghz/protocols/nord_ice.c @@ -0,0 +1,352 @@ +#include "nord_ice.h" +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" + +#define TAG "SubGhzProtocolNord_Ice" + +static const SubGhzBlockConst subghz_protocol_nord_ice_const = { + .te_short = 300, + .te_long = 800, + .te_delta = 150, + .min_count_bit_for_found = 33, +}; + +struct SubGhzProtocolDecoderNord_Ice { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderNord_Ice { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + Nord_IceDecoderStepReset = 0, + Nord_IceDecoderStepSaveDuration, + Nord_IceDecoderStepCheckDuration, +} Nord_IceDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder = { + .alloc = subghz_protocol_decoder_nord_ice_alloc, + .free = subghz_protocol_decoder_nord_ice_free, + + .feed = subghz_protocol_decoder_nord_ice_feed, + .reset = subghz_protocol_decoder_nord_ice_reset, + + .get_hash_data = NULL, + .get_hash_data_long = subghz_protocol_decoder_nord_ice_get_hash_data, + .serialize = subghz_protocol_decoder_nord_ice_serialize, + .deserialize = subghz_protocol_decoder_nord_ice_deserialize, + .get_string = subghz_protocol_decoder_nord_ice_get_string, + .get_string_brief = NULL, +}; + +const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder = { + .alloc = subghz_protocol_encoder_nord_ice_alloc, + .free = subghz_protocol_encoder_nord_ice_free, + + .deserialize = subghz_protocol_encoder_nord_ice_deserialize, + .stop = subghz_protocol_encoder_nord_ice_stop, + .yield = subghz_protocol_encoder_nord_ice_yield, +}; + +const SubGhzProtocol subghz_protocol_nord_ice = { + .name = SUBGHZ_PROTOCOL_NORD_ICE_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_nord_ice_decoder, + .encoder = &subghz_protocol_nord_ice_encoder, +}; + +void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolEncoderNord_Ice)); + + instance->base.protocol = &subghz_protocol_nord_ice; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 3; + instance->encoder.size_upload = 128; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_nord_ice_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderNord_Ice* instance = context; + free(instance->encoder.upload); + free(instance); +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderNord_Ice instance + */ +static void subghz_protocol_encoder_nord_ice_get_upload(SubGhzProtocolEncoderNord_Ice* instance) { + furi_assert(instance); + size_t index = 0; + + // Send key and GAP + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(bit_read(instance->generic.data, i - 1)) { + // Send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_nord_ice_check_remote_controller(SubGhzBlockGeneric* instance) { + instance->serial = (instance->data >> 15) << 9 | + (instance->data & 0x1FF); // 26 bits for serial + instance->btn = (instance->data >> 9) & 0x3F; // 6 bits for button +} + +SubGhzProtocolStatus + subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderNord_Ice* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nord_ice_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_nord_ice_check_remote_controller(&instance->generic); + subghz_protocol_encoder_nord_ice_get_upload(instance); + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_nord_ice_stop(void* context) { + SubGhzProtocolEncoderNord_Ice* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context) { + SubGhzProtocolEncoderNord_Ice* instance = context; + + if(instance->encoder.repeat == 0 || !instance->encoder.is_running) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration ret = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return ret; +} + +void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolDecoderNord_Ice)); + instance->base.protocol = &subghz_protocol_nord_ice; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_nord_ice_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + free(instance); +} + +void subghz_protocol_decoder_nord_ice_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + instance->decoder.parser_step = Nord_IceDecoderStepReset; +} + +void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + + // Nord ICE Decoder + // 2026.03 - @xMasterX (MMX) + + // Key samples + // + // Serial Btn Serial + // 0x9467688A btn 1 = 10010100011001110 110100 010001010 + // 0x9467308A btn 2 = 10010100011001110 011000 010001010 + // 0x9467628A btn 3 = 10010100011001110 110001 010001010 + // 0x9467648A btn 4 = 10010100011001110 110010 010001010 + + switch(instance->decoder.parser_step) { + case Nord_IceDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) < + subghz_protocol_nord_ice_const.te_delta * 11)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration; + } + break; + case Nord_IceDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Nord_IceDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Nord_IceDecoderStepReset; + } + break; + case Nord_IceDecoderStepCheckDuration: + if(!level) { + // Bit 0 is short and long timing = 300us HIGH (te_last) and 800us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) < + subghz_protocol_nord_ice_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_long) < + subghz_protocol_nord_ice_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration; + // Bit 1 is long and short timing = 800us HIGH (te_last) and 300us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) < + subghz_protocol_nord_ice_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short) < + subghz_protocol_nord_ice_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) < + subghz_protocol_nord_ice_const.te_delta * 11) { + //Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes) + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) < + subghz_protocol_nord_ice_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + if((DURATION_DIFF( + instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) < + subghz_protocol_nord_ice_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + // If got 33 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_nord_ice_const.min_count_bit_for_found) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Nord_IceDecoderStepReset; + } else { + instance->decoder.parser_step = Nord_IceDecoderStepReset; + } + } else { + instance->decoder.parser_step = Nord_IceDecoderStepReset; + } + break; + } +} + +uint32_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_nord_ice_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_nord_ice_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderNord_Ice* instance = context; + + subghz_protocol_nord_ice_check_remote_controller(&instance->generic); + + uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit); + + // for future use + // // push protocol data to global variable + // subghz_block_generic_global.btn_is_available = false; + // subghz_block_generic_global.current_btn = instance->generic.btn; + // subghz_block_generic_global.btn_length_bit = 4; + // // + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%08llX\r\n" + "Yek: 0x%08llX\r\n" + "Serial: 0x%07lX\r\n" + "Btn: %02X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint64_t)(instance->generic.data & 0xFFFFFFFFF), + (code_found_reverse & 0xFFFFFFFFF), + instance->generic.serial, + instance->generic.btn); +} diff --git a/lib/subghz/protocols/nord_ice.h b/lib/subghz/protocols/nord_ice.h new file mode 100644 index 000000000..7c7cf7aab --- /dev/null +++ b/lib/subghz/protocols/nord_ice.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_NORD_ICE_NAME "Nord ICE" + +typedef struct SubGhzProtocolDecoderNord_Ice SubGhzProtocolDecoderNord_Ice; +typedef struct SubGhzProtocolEncoderNord_Ice SubGhzProtocolEncoderNord_Ice; + +extern const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder; +extern const SubGhzProtocol subghz_protocol_nord_ice; + +/** + * Allocate SubGhzProtocolEncoderNord_Ice. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderNord_Ice* pointer to a SubGhzProtocolEncoderNord_Ice instance + */ +void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderNord_Ice. + * @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance + */ +void subghz_protocol_encoder_nord_ice_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance + */ +void subghz_protocol_encoder_nord_ice_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderNord_Ice. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderNord_Ice* pointer to a SubGhzProtocolDecoderNord_Ice instance + */ +void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderNord_Ice. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + */ +void subghz_protocol_decoder_nord_ice_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderNord_Ice. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + */ +void subghz_protocol_decoder_nord_ice_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderNord_Ice. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderNord_Ice. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance + * @param output Resulting text + */ +void subghz_protocol_decoder_nord_ice_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 5fb2e4bfd..7e42c5bbe 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -10,6 +10,9 @@ #define TAG "SubGhzProtocolPhoenixV2" +//variable used to bypass CounterMode settings if user just change Counter or Button +static bool bypass = false; + static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = { .te_short = 427, .te_long = 853, @@ -79,7 +82,7 @@ void* subghz_protocol_encoder_phoenix_v2_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_phoenix_v2; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -93,6 +96,8 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) { free(instance); } +static uint8_t v2_phoenix_counter_mode = 0; + // Pre define functions static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter); static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance); @@ -118,9 +123,11 @@ bool subghz_protocol_phoenix_v2_create_data( local_data_rev, instance->generic.cnt); instance->generic.data = subghz_protocol_blocks_reverse_key( - (uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)instance->generic.btn << 32) | - (uint64_t)instance->generic.serial), - instance->generic.data_count_bit + 4); + (uint64_t)(((uint64_t)encrypted_counter << 40) | + ((uint64_t)instance->generic.btn << 32) | + (uint64_t)instance->generic.serial), + instance->generic.data_count_bit + 4) & + 0xFFFFFFFFFFFFF; return SubGhzProtocolStatusOk == subghz_block_generic_serialize(&instance->generic, flipper_format, preset); @@ -253,19 +260,41 @@ static bool // This will override the btn variable if a custom button is set btn = subghz_protocol_phoenix_v2_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) { + bypass = true; + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + } + // Reconstruction of the data - // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { - // standart counter mode. PULL data from subghz_block_generic_global variables - if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { - // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value - if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + // if we change counter/button in SignalSettings menu then we must bypass counter_modes, just gen and save signal file. + if(subghz_block_generic_global.cnt_need_override) bypass = true; + + if(v2_phoenix_counter_mode == 0 || bypass) { + // Check for OFEX (overflow experimental) mode + if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF || bypass) { + bypass = false; + // standart counter mode. PULL data from subghz_block_generic_global variables + if(!subghz_block_generic_global_counter_override_get(&instance->generic.cnt)) { + // if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + } + } + } else { + if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; + } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { + instance->generic.cnt = 0xFFFE; } else { - instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt++; } } - } else { + } else if(v2_phoenix_counter_mode == 1) { + // Mode 1 (ofex like) + // 0000 / 0001 / FFFE / FFFF if((instance->generic.cnt + 0x1) > 0xFFFF) { instance->generic.cnt = 0; } else if(instance->generic.cnt >= 0x1 && instance->generic.cnt != 0xFFFE) { @@ -273,6 +302,14 @@ static bool } else { instance->generic.cnt++; } + } else { + // Mode 2 (0 to 4) + // 0x0000 / 0x0001 / 0x0002 / 0x0003 / 0x0004 + if(instance->generic.cnt >= 0x0004) { + instance->generic.cnt = 0; + } else { + instance->generic.cnt++; + } } uint64_t local_data_rev = subghz_protocol_blocks_reverse_key( @@ -328,6 +365,18 @@ SubGhzProtocolStatus flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + v2_phoenix_counter_mode = (uint8_t)tmp_counter_mode; + } else { + v2_phoenix_counter_mode = 0; + } + subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) { @@ -335,6 +384,11 @@ SubGhzProtocolStatus break; } + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF; @@ -366,7 +420,7 @@ LevelDuration subghz_protocol_encoder_phoenix_v2_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -584,10 +638,25 @@ SubGhzProtocolStatus subghz_protocol_decoder_phoenix_v2_deserialize(void* context, FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderPhoenix_V2* instance = context; - return subghz_block_generic_deserialize_check_count_bit( + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + ret = subghz_block_generic_deserialize_check_count_bit( &instance->generic, flipper_format, subghz_protocol_phoenix_v2_const.min_count_bit_for_found); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + return SubGhzProtocolStatusError; + } + + uint32_t tmp_counter_mode; + if(flipper_format_read_uint32(flipper_format, "CounterMode", &tmp_counter_mode, 1)) { + v2_phoenix_counter_mode = (uint8_t)tmp_counter_mode; + } else { + v2_phoenix_counter_mode = 0; + } + return ret; } void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* output) { @@ -600,6 +669,11 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "V2 Phoenix %dbit\r\n" diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 066bd694e..b143ac4ce 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -87,7 +87,7 @@ void* subghz_protocol_encoder_power_smart_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_power_smart; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 1024; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -238,7 +238,7 @@ LevelDuration subghz_protocol_encoder_power_smart_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -269,7 +269,7 @@ void subghz_protocol_decoder_power_smart_reset(void* context) { NULL); } -bool subghz_protocol_power_smart_chek_valid(uint64_t packet) { +bool subghz_protocol_power_smart_check_valid(uint64_t packet) { uint32_t data_1 = (uint32_t)((packet >> 40) & 0xFFFF); uint32_t data_2 = (uint32_t)((~packet >> 8) & 0xFFFF); uint8_t data_3 = (uint8_t)(packet >> 32) & 0xFF; @@ -313,7 +313,7 @@ void subghz_protocol_decoder_power_smart_feed( } if((instance->decoder.decode_data & POWER_SMART_PACKET_HEADER_MASK) == POWER_SMART_PACKET_HEADER) { - if(subghz_protocol_power_smart_chek_valid(instance->decoder.decode_data)) { + if(subghz_protocol_power_smart_check_valid(instance->decoder.decode_data)) { instance->generic.data = instance->decoder.decode_data; instance->generic.data_count_bit = subghz_protocol_power_smart_const.min_count_bit_for_found; @@ -371,6 +371,12 @@ void subghz_protocol_decoder_power_smart_get_string(void* context, FuriString* o SubGhzProtocolDecoderPowerSmart* instance = context; subghz_protocol_power_smart_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 16cde9880..5be5d82e2 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -97,7 +97,7 @@ void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_princeton; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; //max 24bit*2 + 2 (start, stop) instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -261,6 +261,10 @@ static bool // Generate new key using custom or default button instance->generic.btn = subghz_protocol_princeton_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + // Reconstruction of the data // If we have 8bit button code move serial to left by 8 bits (and 4 if 4 bits) if(instance->generic.btn == 0x30 || instance->generic.btn == 0xC0) { @@ -414,7 +418,7 @@ LevelDuration subghz_protocol_encoder_princeton_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -594,8 +598,14 @@ void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* out uint32_t data_rev = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + // + if(instance->generic.btn == 0x30 || instance->generic.btn == 0xC0 || instance->generic.btn == 0xF3 || instance->generic.btn == 0xFC) { + subghz_block_generic_global.btn_length_bit = 8; furi_string_cat_printf( output, "%s %dbit\r\n" @@ -614,6 +624,7 @@ void subghz_protocol_decoder_princeton_get_string(void* context, FuriString* out instance->te, instance->guard_time); } else { + subghz_block_generic_global.btn_length_bit = 4; furi_string_cat_printf( output, "%s %dbit\r\n" diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index a4c060f61..a82054d7f 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,88 +1,61 @@ #include "protocol_items.h" // IWYU pragma: keep const SubGhzProtocol* const subghz_protocol_registry_items[] = { - // Core - &subghz_protocol_raw, - &subghz_protocol_bin_raw, - &subghz_protocol_princeton, - - // Garage/Gate + &subghz_protocol_gate_tx, &subghz_protocol_keeloq, + &subghz_protocol_nice_flo, &subghz_protocol_came, + &subghz_protocol_faac_slh, + &subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, &subghz_protocol_came_atomo, - &subghz_protocol_nice_flo, - &subghz_protocol_nice_flor_s, - &subghz_protocol_chamb_code, - &subghz_protocol_secplus_v1, - &subghz_protocol_secplus_v2, - &subghz_protocol_linear, - &subghz_protocol_marantec, - &subghz_protocol_marantec24, + &subghz_protocol_nero_sketch, + &subghz_protocol_ido, + &subghz_protocol_hormann, + &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis, - &subghz_protocol_hormann, - &subghz_protocol_hormann_bisecur, - &subghz_protocol_alutech_at_4n, - &subghz_protocol_faac_slh, - &subghz_protocol_phoenix_v2, - &subghz_protocol_beninca_arc, - &subghz_protocol_gate_tx, - &subghz_protocol_bett, - &subghz_protocol_gangqi, - &subghz_protocol_roger, - &subghz_protocol_revers_rb2, - &subghz_protocol_holtek, - &subghz_protocol_holtek_th12x, - &subghz_protocol_clemsa, - &subghz_protocol_magellan, - - // Established Regional - &subghz_protocol_x10, - &subghz_protocol_pocsag, - &subghz_protocol_intertechno_v3, - &subghz_protocol_legrand, - &subghz_protocol_honeywell, - &subghz_protocol_honeywell_wdb, - &subghz_protocol_mastercode, + &subghz_protocol_princeton, + &subghz_protocol_raw, + &subghz_protocol_linear, + &subghz_protocol_secplus_v2, + &subghz_protocol_secplus_v1, &subghz_protocol_megacode, - &subghz_protocol_hollarm, - &subghz_protocol_treadmill37, - - // &subghz_protocol_nero_sketch, - // &subghz_protocol_nero_radio, - // &subghz_protocol_ido, - // &subghz_protocol_power_smart, - // &subghz_protocol_doitrand, - // &subghz_protocol_ansonic, - // &subghz_protocol_smc5326, - // &subghz_protocol_linear_delta3, - // &subghz_protocol_dooya, - // &subghz_protocol_kinggates_stylo_4k, - // &subghz_protocol_dickert_mahs, - // &subghz_protocol_hay21, - // &subghz_protocol_feron, - // &subghz_protocol_elplast, - - // Weather Station - &ws_protocol_oregon2, - &ws_protocol_oregon3, - &ws_protocol_lacrosse_tx, - &ws_protocol_acurite_5n1, - &ws_protocol_ambient_weather, - &ws_protocol_bresser_3ch, - &ws_protocol_acurite_592txr, - &ws_protocol_acurite_606tx, - &ws_protocol_acurite_609txc, - &ws_protocol_lacrosse_tx141thbv2, - + &subghz_protocol_holtek, + &subghz_protocol_chamb_code, + &subghz_protocol_power_smart, + &subghz_protocol_marantec, + &subghz_protocol_bett, + &subghz_protocol_doitrand, + &subghz_protocol_phoenix_v2, + &subghz_protocol_honeywell, // Should be before honeywell_wdb + &subghz_protocol_honeywell_wdb, + &subghz_protocol_magellan, + &subghz_protocol_intertechno_v3, + &subghz_protocol_clemsa, + &subghz_protocol_ansonic, + &subghz_protocol_smc5326, + &subghz_protocol_holtek_th12x, + &subghz_protocol_linear_delta3, + &subghz_protocol_dooya, + &subghz_protocol_alutech_at_4n, + &subghz_protocol_kinggates_stylo_4k, // &ws_protocol_infactory, // &ws_protocol_thermopro_tx4, // &ws_protocol_nexus_th, // &ws_protocol_gt_wt_02, // &ws_protocol_gt_wt_03, + // &ws_protocol_acurite_606tx, + // &ws_protocol_acurite_609txc, // &ws_protocol_acurite_986, - // &ws_protocol_solight_te44, + // &ws_protocol_bresser_3ch, // Should be before lacrosse + // &ws_protocol_lacrosse_tx, + // &ws_protocol_lacrosse_tx141thbv2, + // &ws_protocol_oregon2, + // &ws_protocol_oregon3, + // &ws_protocol_acurite_592txr, + // &ws_protocol_ambient_weather, + // &ws_protocol_solight_te44, // Should be before auriol // &ws_protocol_auriol_th, // &ws_protocol_oregon_v1, // &ws_protocol_tx_8300, @@ -90,7 +63,31 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { // &ws_protocol_auriol_ahfl, // &ws_protocol_kedsum_th, // &ws_protocol_emose601x, + // &ws_protocol_acurite_5n1, // &ws_protocol_vauno_en8822c, + // &subghz_protocol_pocsag, + // &tpms_protocol_schrader_gg4, + &subghz_protocol_bin_raw, + &subghz_protocol_mastercode, + // &subghz_protocol_x10, + // &subghz_protocol_hormann_bisecur, + &subghz_protocol_legrand, + &subghz_protocol_dickert_mahs, + &subghz_protocol_gangqi, + &subghz_protocol_marantec24, + &subghz_protocol_hollarm, + &subghz_protocol_hay21, + &subghz_protocol_revers_rb2, + &subghz_protocol_feron, + &subghz_protocol_roger, + &subghz_protocol_elplast, + &subghz_protocol_treadmill37, + &subghz_protocol_beninca_arc, + &subghz_protocol_jarolift, + &subghz_protocol_ditec_gol4, + &subghz_protocol_keyfinder, + &subghz_protocol_nord_ice, + &subghz_protocol_allstar_firefly, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 2c87f49b1..249e41a89 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -1,94 +1,90 @@ #pragma once #include "../registry.h" #include "../subghz_protocol_registry.h" - -// Core -#include "raw.h" -#include "bin_raw.h" #include "princeton.h" - -// Garage/Gate #include "keeloq.h" +#include "nice_flo.h" #include "came.h" +#include "faac_slh.h" +#include "nice_flor_s.h" #include "came_twee.h" #include "came_atomo.h" -#include "nice_flo.h" -#include "nice_flor_s.h" -#include "chamberlain_code.h" -#include "secplus_v1.h" -#include "secplus_v2.h" -#include "linear.h" -#include "marantec.h" -#include "marantec24.h" +#include "nero_sketch.h" +#include "ido.h" +#include "hormann.h" +#include "nero_radio.h" #include "somfy_telis.h" #include "somfy_keytis.h" -#include "hormann.h" -#include "hormann_bisecur.h" -#include "alutech_at_4n.h" -#include "faac_slh.h" -#include "phoenix_v2.h" -#include "beninca_arc.h" #include "gate_tx.h" -#include "bett.h" -#include "gangqi.h" -#include "roger.h" -#include "revers_rb2.h" +#include "raw.h" +#include "linear.h" +#include "linear_delta3.h" +#include "secplus_v2.h" +#include "secplus_v1.h" +#include "megacode.h" #include "holtek.h" -#include "holtek_ht12x.h" -#include "clemsa.h" -#include "magellan.h" - -// Established Regional -#include "x10.h" -#include "pocsag.h" -#include "intertechno_v3.h" -#include "legrand.h" +#include "chamberlain_code.h" +#include "power_smart.h" +#include "marantec.h" +#include "bett.h" +#include "doitrand.h" +#include "phoenix_v2.h" #include "honeywell.h" #include "honeywell_wdb.h" -#include "mastercode.h" -#include "megacode.h" -#include "hollarm.h" -#include "nero_sketch.h" -#include "nero_radio.h" -#include "ido.h" -#include "power_smart.h" -#include "doitrand.h" +#include "magellan.h" +#include "intertechno_v3.h" +#include "clemsa.h" #include "ansonic.h" #include "smc5326.h" -#include "linear_delta3.h" +#include "holtek_ht12x.h" #include "dooya.h" +#include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" -#include "dickert_mahs.h" -#include "hay21.h" -#include "feron.h" -#include "elplast.h" -#include "schrader_gg4.h" -#include "tpms_generic.h" -#include "treadmill37.h" - -// Weather Station -#include "oregon2.h" -#include "oregon3.h" -#include "lacrosse_tx.h" -#include "acurite_5n1.h" -#include "ambient_weather.h" -#include "bresser_3ch.h" -#include "acurite_592txr.h" -#include "acurite_606tx.h" -#include "acurite_609txc.h" -#include "lacrosse_tx141thbv2.h" -#include "solight_te44.h" #include "infactory.h" -#include "acurite_986.h" -#include "auriol_ahfl.h" -#include "auriol_hg0601a.h" -#include "emos_e601x.h" +#include "thermopro_tx4.h" +#include "nexus_th.h" #include "gt_wt_02.h" #include "gt_wt_03.h" -#include "kedsum_th.h" -#include "nexus_th.h" +#include "acurite_606tx.h" +#include "acurite_609txc.h" +#include "acurite_986.h" +#include "lacrosse_tx.h" +#include "lacrosse_tx141thbv2.h" +#include "oregon2.h" +#include "oregon3.h" +#include "acurite_592txr.h" +#include "ambient_weather.h" +#include "auriol_hg0601a.h" #include "oregon_v1.h" -#include "thermopro_tx4.h" #include "tx_8300.h" -#include "vauno_en8822c.h" #include "wendox_w6726.h" +#include "auriol_ahfl.h" +#include "kedsum_th.h" +#include "emos_e601x.h" +#include "acurite_5n1.h" +#include "solight_te44.h" +#include "bresser_3ch.h" +#include "vauno_en8822c.h" +#include "pocsag.h" +#include "schrader_gg4.h" +#include "bin_raw.h" +#include "mastercode.h" +#include "x10.h" +#include "hormann_bisecur.h" +#include "legrand.h" +#include "dickert_mahs.h" +#include "gangqi.h" +#include "marantec24.h" +#include "hollarm.h" +#include "hay21.h" +#include "revers_rb2.h" +#include "feron.h" +#include "roger.h" +#include "elplast.h" +#include "treadmill37.h" +#include "beninca_arc.h" +#include "jarolift.h" +#include "ditec_gol4.h" +#include "keyfinder.h" +#include "nord_ice.h" +#include "allstar_firefly.h" diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index b2997a484..678d7582e 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -57,7 +57,7 @@ bool subghz_protocol_keeloq_create_data( * @param preset Modulation, SubGhzRadioPreset * @return true On success */ -bool subghz_protocol_keeloq_bft_create_data( +bool subghz_protocol_keeloq_seed_create_data( void* context, FlipperFormat* flipper_format, uint32_t serial, @@ -231,6 +231,42 @@ bool subghz_protocol_beninca_arc_create_data( uint32_t cnt, SubGhzRadioPreset* preset); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderJarolift instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number, 24 bit + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_jarolift_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number + * @param btn Button number, 8 bit + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_ditec_gol4_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint8_t btn, + uint16_t cnt, + SubGhzRadioPreset* preset); + typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; void subghz_protocol_decoder_bin_raw_data_input_rssi( @@ -248,6 +284,14 @@ bool subghz_protocol_secplus_v1_check_fixed(uint32_t fixed); // TODO: Remake in proper way void faac_slh_reset_prog_mode(void); +/** + * Calculate CRC8 for Marantec protocol. + * @param data Pointer to the data buffer + * @param len Length of the data buffer + * @return CRC8 value + */ +uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len); + #ifdef __cplusplus } #endif diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 71cb18b9a..c6561dc7d 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -101,10 +101,6 @@ bool subghz_protocol_raw_save_to_file_init( if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { break; } - // Create saved directory if necessary - if(!storage_simply_mkdir(instance->storage, SUBGHZ_RAW_FOLDER)) { - break; - } furi_string_set(instance->file_name, dev_name); // First remove subghz device file if it was saved diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 017c385b0..84a8be706 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -79,8 +79,8 @@ void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_revers_rb2; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 1768; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -130,30 +130,27 @@ static void furi_assert(instance); size_t index = 0; - for(size_t r = 0; r < 6; r++) { - ManchesterEncoderState enc_state; - manchester_encoder_reset(&enc_state); - ManchesterEncoderResult result; + ManchesterEncoderState enc_state; + manchester_encoder_reset(&enc_state); + ManchesterEncoderResult result; - for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { - if(!manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result)) { - instance->encoder.upload[index++] = - subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); - manchester_encoder_advance( - &enc_state, bit_read(instance->generic.data, i - 1), &result); - } + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result)) { instance->encoder.upload[index++] = subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); + manchester_encoder_advance( + &enc_state, bit_read(instance->generic.data, i - 1), &result); } - instance->encoder.upload[index] = - subghz_protocol_encoder_revers_rb2_add_duration_to_upload( - manchester_encoder_finish(&enc_state)); - if(level_duration_get_level(instance->encoder.upload[index])) { - index++; - } - instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320); + instance->encoder.upload[index++] = + subghz_protocol_encoder_revers_rb2_add_duration_to_upload(result); } + instance->encoder.upload[index] = subghz_protocol_encoder_revers_rb2_add_duration_to_upload( + manchester_encoder_finish(&enc_state)); + if(level_duration_get_level(instance->encoder.upload[index])) { + index++; + } + instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)320); instance->encoder.size_upload = index; } @@ -186,7 +183,6 @@ SubGhzProtocolStatus subghz_protocol_revers_rb2_remote_controller(&instance->generic); subghz_protocol_encoder_revers_rb2_get_upload(instance); - instance->encoder.front = 0; instance->encoder.is_running = true; } while(false); @@ -196,7 +192,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_revers_rb2_stop(void* context) { SubGhzProtocolEncoderRevers_RB2* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) { @@ -210,7 +205,7 @@ LevelDuration subghz_protocol_encoder_revers_rb2_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 45237999c..35d80788d 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -76,8 +76,8 @@ void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_roger; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -178,6 +178,10 @@ static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* // This will override the btn variable if a custom button is set btn = subghz_protocol_roger_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + // If End is not == button - transmit as is, no custom button allowed // For "End" values 23 and 20 - transmit correct ending used for their buttons if((instance->generic.data & 0xFF) == instance->generic.btn) { @@ -271,7 +275,6 @@ SubGhzProtocolStatus subghz_protocol_roger_check_remote_controller(&instance->generic); subghz_protocol_encoder_roger_get_upload(instance); - instance->encoder.front = 0; uint8_t key_data[sizeof(uint64_t)] = {0}; for(size_t i = 0; i < sizeof(uint64_t); i++) { @@ -291,7 +294,6 @@ SubGhzProtocolStatus void subghz_protocol_encoder_roger_stop(void* context) { SubGhzProtocolEncoderRoger* instance = context; instance->encoder.is_running = false; - instance->encoder.front = 0; } LevelDuration subghz_protocol_encoder_roger_yield(void* context) { @@ -305,7 +307,7 @@ LevelDuration subghz_protocol_encoder_roger_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -435,6 +437,12 @@ void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) subghz_protocol_roger_check_remote_controller(&instance->generic); + // push protocol data to global variable + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 7571e4133..90b687c4a 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -100,7 +100,7 @@ void* subghz_protocol_encoder_secplus_v1_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_secplus_v1; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -346,7 +346,7 @@ LevelDuration subghz_protocol_encoder_secplus_v1_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -588,6 +588,11 @@ void subghz_protocol_decoder_secplus_v1_get_string(void* context, FuriString* ou subghz_block_generic_global.cnt_length_bit = 32; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = false; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 2; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index f20e7c685..3f82fb47a 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -94,7 +94,7 @@ void* subghz_protocol_encoder_secplus_v2_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_secplus_v2; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -397,6 +397,10 @@ static void subghz_protocol_secplus_v2_encode(SubGhzProtocolEncoderSecPlus_v2* i instance->generic.btn = subghz_protocol_secplus_v2_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&instance->generic.btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + uint32_t fixed_1[1] = {instance->generic.btn << 12 | instance->generic.serial >> 20}; uint32_t fixed_2[1] = {instance->generic.serial & 0xFFFFF}; uint8_t rolling_digits[18] = {0}; @@ -625,7 +629,7 @@ LevelDuration subghz_protocol_encoder_secplus_v2_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -968,6 +972,11 @@ void subghz_protocol_decoder_secplus_v2_get_string(void* context, FuriString* ou subghz_block_generic_global.cnt_length_bit = 28; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 8; + // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 954633178..8baaa7e5a 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -103,7 +103,7 @@ void* subghz_protocol_encoder_smc5326_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_smc5326; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -210,7 +210,7 @@ LevelDuration subghz_protocol_encoder_smc5326_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index da08c26ac..452991f52 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -7,6 +7,8 @@ #include "../blocks/generic.h" #include "../blocks/math.h" +#include "../blocks/custom_btn_i.h" + #define TAG "SubGhzProtocolSomfyKeytis" static const SubGhzBlockConst subghz_protocol_somfy_keytis_const = { @@ -24,7 +26,6 @@ struct SubGhzProtocolDecoderSomfyKeytis { uint16_t header_count; ManchesterState manchester_saved_state; - uint32_t press_duration_counter; }; struct SubGhzProtocolEncoderSomfyKeytis { @@ -83,7 +84,7 @@ void* subghz_protocol_encoder_somfy_keytis_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_somfy_keytis; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -124,13 +125,49 @@ void subghz_protocol_decoder_somfy_keytis_reset(void* context) { NULL); } +static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGeneric* instance); + +static uint8_t subghz_protocol_somfy_keytis_get_btn_code(void) { + uint8_t custom_btn_id = subghz_custom_btn_get(); + uint8_t original_btn_code = subghz_custom_btn_get_original(); + uint8_t btn = original_btn_code; + + // Set custom button + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + // Restore original button code + btn = original_btn_code; + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { + switch(original_btn_code) { + case 0x4: + btn = 0x3; + break; + case 0x3: + btn = 0x4; + break; + + default: + break; + } + } + + return btn; +} + static bool subghz_protocol_somfy_keytis_gen_data(SubGhzProtocolEncoderSomfyKeytis* instance, uint8_t btn) { - UNUSED(btn); - uint64_t data = instance->generic.data ^ (instance->generic.data >> 8); - instance->generic.btn = (data >> 48) & 0xF; - instance->generic.cnt = (data >> 24) & 0xFFFF; - instance->generic.serial = data & 0xFFFFFF; + //instance->generic.btn = (data >> 48) & 0xF; + //instance->generic.cnt = (data >> 24) & 0xFFFF; + //instance->generic.serial = data & 0xFFFFFF; + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + btn = subghz_protocol_somfy_keytis_get_btn_code(); + + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); // Check for OFEX (overflow experimental) mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { @@ -154,7 +191,7 @@ static bool } uint8_t frame[10]; - frame[0] = (0xA << 4) | instance->generic.btn; + frame[0] = (0xA << 4) | btn; frame[1] = 0xF << 4; frame[2] = instance->generic.cnt >> 8; frame[3] = instance->generic.cnt; @@ -176,7 +213,7 @@ static bool for(uint8_t i = 1; i < 7; i++) { frame[i] ^= frame[i - 1]; } - data = 0; + uint64_t data = 0; for(uint8_t i = 0; i < 7; ++i) { data <<= 8; data |= frame[i]; @@ -187,7 +224,7 @@ static bool data <<= 8; data |= frame[i]; } - instance->generic.data_2 = data; + instance->generic.seed = data; return true; } @@ -203,12 +240,25 @@ bool subghz_protocol_somfy_keytis_create_data( instance->generic.serial = serial; instance->generic.cnt = cnt; instance->generic.data_count_bit = 80; - bool res = subghz_protocol_somfy_keytis_gen_data(instance, btn); - if(res) { - return SubGhzProtocolStatusOk == - subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + subghz_protocol_somfy_keytis_gen_data(instance, btn); + + // Encode complete, now serialize + SubGhzProtocolStatus res = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; } - return res; + + if((res == SubGhzProtocolStatusOk) && + !flipper_format_insert_or_update_uint32( + flipper_format, "Duration_Counter", &instance->generic.seed, 1)) { + FURI_LOG_E(TAG, "Unable to add Duration_Counter"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + return res == SubGhzProtocolStatusOk; } /** @@ -274,7 +324,7 @@ static bool subghz_protocol_encoder_somfy_keytis_get_upload( } for(uint8_t i = 24; i > 0; i--) { - if(bit_read(instance->generic.data_2, i - 1)) { + if(bit_read(instance->generic.seed, i - 1)) { if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { instance->encoder.upload[index - 1].duration *= 2; // 00 instance->encoder.upload[index++] = level_duration_make( @@ -352,7 +402,7 @@ static bool subghz_protocol_encoder_somfy_keytis_get_upload( } for(uint8_t i = 24; i > 0; i--) { - if(bit_read(instance->generic.data_2, i - 1)) { + if(bit_read(instance->generic.seed, i - 1)) { if(instance->encoder.upload[index - 1].level == LEVEL_DURATION_LEVEL_LOW) { instance->encoder.upload[index - 1].duration *= 2; // 00 instance->encoder.upload[index++] = level_duration_make( @@ -418,6 +468,8 @@ SubGhzProtocolStatus flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_somfy_keytis_check_remote_controller(&instance->generic); + subghz_protocol_encoder_somfy_keytis_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { @@ -457,7 +509,7 @@ LevelDuration subghz_protocol_encoder_somfy_keytis_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -513,7 +565,7 @@ void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32 instance->decoder.parser_step = SomfyKeytisDecoderStepDecoderData; instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; - instance->press_duration_counter = 0; + instance->generic.seed = 0; manchester_advance( instance->manchester_saved_state, ManchesterEventReset, @@ -590,8 +642,7 @@ void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32 if(instance->decoder.decode_count_bit < 56) { instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data; } else { - instance->press_duration_counter = (instance->press_duration_counter << 1) | - data; + instance->generic.seed = (instance->generic.seed << 1) | data; } instance->decoder.decode_count_bit++; @@ -709,6 +760,12 @@ static void subghz_protocol_somfy_keytis_check_remote_controller(SubGhzBlockGene instance->btn = (data >> 48) & 0xF; instance->cnt = (data >> 24) & 0xFFFF; instance->serial = data & 0xFFFFFF; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(instance->btn); + } + subghz_custom_btn_set_max(1); } /** @@ -753,7 +810,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_somfy_keytis_serialize( subghz_block_generic_serialize(&instance->generic, flipper_format, preset); if((ret == SubGhzProtocolStatusOk) && !flipper_format_write_uint32( - flipper_format, "Duration_Counter", &instance->press_duration_counter, 1)) { + flipper_format, "Duration_Counter", &instance->generic.seed, 1)) { FURI_LOG_E(TAG, "Unable to add Duration_Counter"); ret = SubGhzProtocolStatusErrorParserOthers; } @@ -779,10 +836,7 @@ SubGhzProtocolStatus break; } if(!flipper_format_read_uint32( - flipper_format, - "Duration_Counter", - (uint32_t*)&instance->press_duration_counter, - 1)) { + flipper_format, "Duration_Counter", (uint32_t*)&instance->generic.seed, 1)) { FURI_LOG_E(TAG, "Missing Duration_Counter"); ret = SubGhzProtocolStatusErrorParserOthers; break; @@ -803,20 +857,26 @@ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" "%lX%08lX%06lX\r\n" "Sn:0x%06lX \r\n" "Cnt:%04lX\r\n" - "Btn:%s\r\n", + "Btn:%X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), (uint32_t)instance->generic.data, - instance->press_duration_counter, + instance->generic.seed, instance->generic.serial, instance->generic.cnt, + instance->generic.btn, subghz_protocol_somfy_keytis_get_name_button(instance->generic.btn)); } diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index f6de27453..8cd051bc7 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -84,7 +84,7 @@ void* subghz_protocol_encoder_somfy_telis_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_somfy_telis; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; + instance->encoder.repeat = 3; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -126,6 +126,10 @@ static bool subghz_protocol_somfy_telis_gen_data( btn = subghz_protocol_somfy_telis_get_btn_code(); + // override button if we change it with signal settings button editor + if(subghz_block_generic_global_button_override_get(&btn)) + FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", btn); + // Check for OFEX (overflow experimental) mode if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { // standart counter mode. PULL data from subghz_block_generic_global variables @@ -386,7 +390,7 @@ LevelDuration subghz_protocol_encoder_somfy_telis_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -760,13 +764,18 @@ void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* o subghz_block_generic_global.cnt_length_bit = 16; subghz_block_generic_global.current_cnt = instance->generic.cnt; + subghz_block_generic_global.btn_is_available = true; + subghz_block_generic_global.current_btn = instance->generic.btn; + subghz_block_generic_global.btn_length_bit = 4; + // + furi_string_cat_printf( output, "%s %db\r\n" "Key:0x%lX%08lX\r\n" "Sn:0x%06lX \r\n" "Cnt:%04lX\r\n" - "Btn:%s\r\n", + "Btn:%X - %s\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, @@ -774,5 +783,6 @@ void subghz_protocol_decoder_somfy_telis_get_string(void* context, FuriString* o (uint32_t)instance->generic.data, instance->generic.serial, instance->generic.cnt, + instance->generic.btn, subghz_protocol_somfy_telis_get_name_button(instance->generic.btn)); } diff --git a/lib/subghz/protocols/treadmill37.c b/lib/subghz/protocols/treadmill37.c index 7d30a33ef..fc839cced 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -75,8 +75,8 @@ void* subghz_protocol_encoder_treadmill37_alloc(SubGhzEnvironment* environment) instance->base.protocol = &subghz_protocol_treadmill37; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 10; - instance->encoder.size_upload = 256; + instance->encoder.repeat = 3; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -181,7 +181,7 @@ LevelDuration subghz_protocol_encoder_treadmill37_yield(void* context) { LevelDuration ret = instance->encoder.upload[instance->encoder.front]; if(++instance->encoder.front == instance->encoder.size_upload) { - instance->encoder.repeat--; + if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--; instance->encoder.front = 0; } @@ -335,6 +335,13 @@ void subghz_protocol_decoder_treadmill37_get_string(void* context, FuriString* o uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key( instance->generic.data, instance->generic.data_count_bit); + // for future use + // // push protocol data to global variable + // subghz_block_generic_global.btn_is_available = false; + // subghz_block_generic_global.current_btn = instance->generic.btn; + // subghz_block_generic_global.btn_length_bit = 4; + // // + furi_string_cat_printf( output, "%s %db\r\n" diff --git a/scripts/fbt/appmanifest.py b/scripts/fbt/appmanifest.py index 4f6a2416c..9b576fb28 100644 --- a/scripts/fbt/appmanifest.py +++ b/scripts/fbt/appmanifest.py @@ -68,6 +68,7 @@ class FlipperApplication: # .fap-specific sources: List[str] = field(default_factory=lambda: ["*.c*"]) + cflags: List[str] = field(default_factory=list) fap_version: Union[str, Tuple[int]] = "0.1" fap_icon: Optional[str] = None fap_libs: List[str] = field(default_factory=list) diff --git a/scripts/fbt_tools/fbt_extapps.py b/scripts/fbt_tools/fbt_extapps.py index 2aa2da30d..0cd33d3a9 100644 --- a/scripts/fbt_tools/fbt_extapps.py +++ b/scripts/fbt_tools/fbt_extapps.py @@ -57,6 +57,7 @@ class AppBuilder: FAP_WORK_DIR=self.app_work_dir, ) self.app_env.Append( + CCFLAGS=self.app.cflags, CPPDEFINES=[ ("FAP_VERSION", f'\\"{".".join(map(str, self.app.fap_version))}\\"'), *self.app.cdefines, @@ -89,7 +90,7 @@ class AppBuilder: fap_icons = self.app_env.CompileIcons( self.app_work_dir, self.app._appdir.Dir(self.app.fap_icon_assets), - icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid }_icons", + icon_bundle_name=f"{self.app.fap_icon_assets_symbol or self.app.appid}_icons", add_include=True, ) self.app_env.Alias("_fap_icons", fap_icons) @@ -420,7 +421,7 @@ def _validate_app_imports(target, source, env): if env.get("_CHECK_APP"): raise UserError(warning_msg) else: - SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg), + SCons.Warnings.warn(SCons.Warnings.LinkWarning, warning_msg) def GetExtAppByIdOrPath(env, app_dir): diff --git a/scripts/ufbt/project_template/.clangd b/scripts/ufbt/project_template/.clangd index 12e13751d..0e6e141bd 100644 --- a/scripts/ufbt/project_template/.clangd +++ b/scripts/ufbt/project_template/.clangd @@ -4,6 +4,10 @@ CompileFlags: - -Wno-format Remove: - -mword-relocations + CompilationDatabase: "./.vscode" + +Completion: + HeaderInsertion: Never Diagnostics: ClangTidy: diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index b224e9eb3..40d3c18dd 100644 --- a/site_scons/commandline.scons +++ b/site_scons/commandline.scons @@ -69,7 +69,7 @@ vars.AddVariables( BoolVariable( "DEBUG", help="Enable debug build", - default=True, + default=False, ), BoolVariable( "LIB_DEBUG", @@ -79,7 +79,7 @@ vars.AddVariables( BoolVariable( "COMPACT", help="Optimize for size", - default=False, + default=True, ), EnumVariable( "TARGET_HW", diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index e881adfb6..ff06eb798 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1774,7 +1774,7 @@ Function,+,gui_remove_framebuffer_callback,void,"Gui*, GuiCanvasCommitCallback, Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool" -Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" +Function,+,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 2d8f342d3..b13bc3e4e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2,7 +2,6 @@ entry,status,name,type,params Version,+,87.1,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/main/archive/helpers/archive_helpers_ext.h,, -Header,+,applications/main/subghz/subghz_fap.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/bt/bt_service/bt_keys_storage.h,, @@ -877,6 +876,8 @@ Function,+,canvas_draw_str_aligned,void,"Canvas*, int32_t, int32_t, Align, Align Function,+,canvas_draw_triangle,void,"Canvas*, int32_t, int32_t, size_t, size_t, CanvasDirection" Function,+,canvas_draw_xbm,void,"Canvas*, int32_t, int32_t, size_t, size_t, const uint8_t*" Function,+,canvas_draw_xbm_ex,void,"Canvas*, int32_t, int32_t, size_t, size_t, IconRotation, const uint8_t*" +Function,+,canvas_get_buffer,uint8_t*,Canvas* +Function,+,canvas_get_buffer_size,size_t,const Canvas* Function,+,canvas_get_font_params,const CanvasFontParameters*,"const Canvas*, Font" Function,+,canvas_glyph_width,size_t,"Canvas*, uint16_t" Function,+,canvas_height,size_t,const Canvas* @@ -2079,7 +2080,7 @@ Function,+,gui_remove_view_port,void,"Gui*, ViewPort*" Function,+,gui_set_hide_statusbar,void,"Gui*, _Bool" Function,+,gui_set_lockdown,void,"Gui*, _Bool" Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool" -Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*" +Function,+,gui_view_port_send_to_back,void,"Gui*, ViewPort*" Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*" Function,-,hci_send_req,int,"hci_request*, uint8_t" Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*" @@ -2350,11 +2351,14 @@ Function,+,iso15693_3_is_equal,_Bool,"const Iso15693_3Data*, const Iso15693_3Dat Function,+,iso15693_3_load,_Bool,"Iso15693_3Data*, FlipperFormat*, uint32_t" Function,+,iso15693_3_poller_activate,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3Data*" Function,+,iso15693_3_poller_get_blocks_security,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t" +Function,+,iso15693_3_poller_get_data,const Iso15693_3Data*,Iso15693_3Poller* Function,+,iso15693_3_poller_get_system_info,Iso15693_3Error,"Iso15693_3Poller*, Iso15693_3SystemInfo*" Function,+,iso15693_3_poller_inventory,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*" Function,+,iso15693_3_poller_read_block,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint8_t, uint8_t" Function,+,iso15693_3_poller_read_blocks,Iso15693_3Error,"Iso15693_3Poller*, uint8_t*, uint16_t, uint8_t" Function,+,iso15693_3_poller_send_frame,Iso15693_3Error,"Iso15693_3Poller*, const BitBuffer*, BitBuffer*, uint32_t" +Function,+,iso15693_3_poller_write_block,Iso15693_3Error,"Iso15693_3Poller*, const uint8_t*, uint8_t, uint8_t" +Function,+,iso15693_3_poller_write_blocks,Iso15693_3Error,"Iso15693_3Poller*, const uint8_t*, uint16_t, uint8_t" Function,+,iso15693_3_reset,void,Iso15693_3Data* Function,+,iso15693_3_save,_Bool,"const Iso15693_3Data*, FlipperFormat*" Function,+,iso15693_3_set_uid,_Bool,"Iso15693_3Data*, const uint8_t*, size_t" @@ -3394,6 +3398,8 @@ Function,+,slix_poller_get_random_number,SlixError,"SlixPoller*, SlixRandomNumbe Function,+,slix_poller_read_signature,SlixError,"SlixPoller*, SlixSignature*" Function,+,slix_poller_send_frame,SlixError,"SlixPoller*, const BitBuffer*, BitBuffer*, uint32_t" Function,+,slix_poller_set_password,SlixError,"SlixPoller*, SlixPasswordType, SlixPassword, SlixRandomNumber" +Function,+,slix_poller_write_block,SlixError,"SlixPoller*, const uint8_t*, uint8_t, uint8_t" +Function,+,slix_poller_write_blocks,SlixError,"SlixPoller*, const uint8_t*, uint16_t, uint8_t" Function,+,slix_reset,void,SlixData* Function,+,slix_save,_Bool,"const SlixData*, FlipperFormat*" Function,+,slix_set_uid,_Bool,"SlixData*, const uint8_t*, size_t" @@ -3615,10 +3621,11 @@ Function,-,strupr,char*,char* Function,-,strverscmp,int,"const char*, const char*" Function,-,strxfrm,size_t,"char*, const char*, size_t" Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" -Function,+,subghz_app,int32_t,char* Function,+,subghz_block_generic_deserialize,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*" Function,+,subghz_block_generic_deserialize_check_count_bit,SubGhzProtocolStatus,"SubGhzBlockGeneric*, FlipperFormat*, uint16_t" Function,+,subghz_block_generic_get_preset_name,void,"const char*, FuriString*" +Function,+,subghz_block_generic_global_button_override_get,_Bool,uint8_t* +Function,+,subghz_block_generic_global_button_override_set,void,uint8_t Function,+,subghz_block_generic_global_counter_override_get,_Bool,uint32_t* Function,+,subghz_block_generic_global_counter_override_set,void,uint32_t Function,+,subghz_block_generic_global_reset,void,void* @@ -3728,15 +3735,18 @@ Function,+,subghz_protocol_decoder_raw_feed,void,"void*, _Bool, uint32_t" Function,+,subghz_protocol_decoder_raw_free,void,void* Function,+,subghz_protocol_decoder_raw_get_string,void,"void*, FuriString*" Function,+,subghz_protocol_decoder_raw_reset,void,void* +Function,+,subghz_protocol_ditec_gol4_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_encoder_raw_alloc,void*,SubGhzEnvironment* Function,+,subghz_protocol_encoder_raw_deserialize,SubGhzProtocolStatus,"void*, FlipperFormat*" Function,+,subghz_protocol_encoder_raw_free,void,void* Function,+,subghz_protocol_encoder_raw_stop,void,void* Function,+,subghz_protocol_encoder_raw_yield,LevelDuration,void* Function,+,subghz_protocol_faac_slh_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint32_t, uint32_t, const char*, SubGhzRadioPreset*" -Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_jarolift_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" +Function,+,subghz_protocol_keeloq_seed_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_kinggates_stylo_4k_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" +Function,+,subghz_protocol_marantec_crc8,uint8_t,"uint8_t*, size_t" Function,+,subghz_protocol_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" Function,+,subghz_protocol_phoenix_v2_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_raw_file_encoder_worker_set_callback_end,void,"SubGhzProtocolEncoderRAW*, SubGhzProtocolEncoderRAWCallbackEnd, void*" diff --git a/targets/f7/furi_hal/furi_hal_bt.c b/targets/f7/furi_hal/furi_hal_bt.c index f1881f94f..4c619c2b8 100644 --- a/targets/f7/furi_hal/furi_hal_bt.c +++ b/targets/f7/furi_hal/furi_hal_bt.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index dbb52e8ce..04e3f5dff 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -699,13 +699,17 @@ static inline uint32_t furi_hal_subghz_async_tx_middleware_get_duration( } } } - +// here we fill DMA buffer by signal durations until we recieve duration=0 (that mean protocol give as full data = signal_size*repeats) +// or until we reach the end of required samples count static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { furi_check(furi_hal_subghz.state == SubGhzStateAsyncTx); - + // furi_hal_subghz_async_tx.callback - linked to protocols "_yield" function + // and return one current LevelDuration from protocol upload buffer. while(samples > 0) { volatile uint32_t duration = furi_hal_subghz_async_tx_middleware_get_duration( &furi_hal_subghz_async_tx.middleware, furi_hal_subghz_async_tx.callback); + // if duration == 0 then we stop DMA interrupt(that used to refill buffer) and write to buffer 0 as last element. + // later DMA write this 0 to ARR and timer TIM2 will be stopped. if(duration == 0) { *buffer = 0; buffer++; @@ -778,48 +782,64 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx.buffer = malloc(FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL * sizeof(uint32_t)); - // Connect CC1101_GD0 to TIM2 as output + // Here we use TIM2_CH2 (Timer 2 Channel 2) to generate HI/LOW signals for C1101 with current durations. + // DMA update/rewrite TIM2 settings (ARR) with new duration each time TIM2 completes. + // Every time when timer counter exeed current TIM2-ARR (AutoReload Register) value timer generate event that call DMA + // DMA load next new value from buffer to TIM2-ARR and timer start count up from 0 to new value again + // Totally we have timer that generate events and update they settings with new durations by DMA action. + // When duration = 0 then DMA wirte 0 to ARR. So when we set ARR=0 - thats mean TIM2 stop counting. + + // Connect CC1101_GD0 to TIM2 as output (Pin B3 - GpioAltFn1TIM2 - TIM2, CH2) furi_hal_gpio_init_ex( &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); - // Configure DMA - LL_DMA_InitTypeDef dma_config = {0}; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); - dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_async_tx.buffer; - dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; - dma_config.Mode = LL_DMA_MODE_CIRCULAR; - dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; - dma_config.MemoryOrM2MDstIncMode = LL_DMA_MEMORY_INCREMENT; - dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; - dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; - dma_config.NbData = FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; - dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; + // Configure DMA to update TIM2->ARR + LL_DMA_InitTypeDef dma_config = {0}; // DMA settings structure + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (TIM2->ARR); // DMA destination TIM2->ARR + dma_config.MemoryOrM2MDstAddress = + (uint32_t)furi_hal_subghz_async_tx.buffer; // DMA buffer with signals durations + dma_config.Direction = + LL_DMA_DIRECTION_MEMORY_TO_PERIPH; // DMA direction from memory to periperhal + dma_config.Mode = LL_DMA_MODE_CIRCULAR; // DMA mode + dma_config.PeriphOrM2MSrcIncMode = + LL_DMA_PERIPH_NOINCREMENT; // DMA destination not changed - allways stay on ARR (AutoReload Register) + dma_config.MemoryOrM2MDstIncMode = + LL_DMA_MEMORY_INCREMENT; // DMA source increment - step by step on durations buffer + dma_config.PeriphOrM2MSrcDataSize = LL_DMA_PDATAALIGN_WORD; // DMA source packet size + dma_config.MemoryOrM2MDstDataSize = LL_DMA_MDATAALIGN_WORD; // DMA destination packet size + dma_config.NbData = FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL; // DMA buffer size + dma_config.PeriphRequest = LL_DMAMUX_REQ_TIM2_UP; // DMA start by TIM2 event dma_config.Priority = LL_DMA_PRIORITY_VERYHIGH; // Ensure that ARR is updated before anyone else try to check it - LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); + LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); // Setup DMA with settings structure + // setup interrupt for DMA. When DMA generate interrupt event we call furi_hal_subghz_async_tx_dma_isr furi_hal_interrupt_set_isr(SUBGHZ_DMA_CH1_IRQ, furi_hal_subghz_async_tx_dma_isr, NULL); - LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); - LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); - LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); // interrupt for full buffer sent + LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); // interrupt for half buffer sent + LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); // Enable - furi_hal_bus_enable(FuriHalBusTIM2); + furi_hal_bus_enable(FuriHalBusTIM2); // Enable TIM2 // Configure TIM2 - LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); + LL_TIM_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); // TIM2 set counter mode UP + // Set the division ratio between the timer clock and the sampling clock 1:1 LL_TIM_SetClockDivision(TIM2, LL_TIM_CLOCKDIVISION_DIV1); + LL_TIM_SetPrescaler(TIM2, 64 - 1); // Perscaler 64 Mghz/64 = 1 Mghz (1 000 000 tick/sec) + // AutoReload Register (ARR) 1000 ticks = 1/1000 Mghz = 1 millisecond, will be changed by DMA by new durations LL_TIM_SetAutoReload(TIM2, 1000); - LL_TIM_SetPrescaler(TIM2, 64 - 1); - LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); - LL_TIM_DisableARRPreload(TIM2); + LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); // ClockSource for TIM2 + LL_TIM_DisableARRPreload( + TIM2); // Change TIM2 setting immediately (dont wait when counter will be overload) // Configure TIM2 CH2 - LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; //Settings structure + // CH2 working mode - TOGGLE (swith between HI and LOW levels) TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_TOGGLE; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_DISABLE; TIM_OC_InitStruct.OCNState = LL_TIM_OCSTATE_DISABLE; - TIM_OC_InitStruct.CompareValue = 0; - TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; - LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct); + TIM_OC_InitStruct.CompareValue = 0; // Counter value to generate events and TOGGLE output + TIM_OC_InitStruct.OCPolarity = LL_TIM_OCPOLARITY_HIGH; // Initial CH2 state - HIGH level + LL_TIM_OC_Init(TIM2, LL_TIM_CHANNEL_CH2, &TIM_OC_InitStruct); // Apply settings to CH2 LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_DisableMasterSlaveMode(TIM2); @@ -827,8 +847,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_async_tx_refill( furi_hal_subghz_async_tx.buffer, FURI_HAL_SUBGHZ_ASYNC_TX_BUFFER_FULL); - LL_TIM_EnableDMAReq_UPDATE(TIM2); - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); + LL_TIM_EnableDMAReq_UPDATE(TIM2); // Setup calling DMA by TIM2 events + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); //Enable TIM2 CH2 // Start debug if(furi_hal_subghz_start_debug()) { @@ -863,8 +883,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* #endif furi_hal_subghz_tx(); - LL_TIM_SetCounter(TIM2, 0); - LL_TIM_EnableCounter(TIM2); + LL_TIM_SetCounter(TIM2, 0); // Reset TIM2 + LL_TIM_EnableCounter(TIM2); // Start TIM2 counting. return true; } diff --git a/targets/f7/furi_hal/furi_hal_usb_hid.c b/targets/f7/furi_hal/furi_hal_usb_hid.c index 04f2ae400..7820ae8d2 100644 --- a/targets/f7/furi_hal/furi_hal_usb_hid.c +++ b/targets/f7/furi_hal/furi_hal_usb_hid.c @@ -7,8 +7,9 @@ #include "usb.h" #include "usb_hid.h" -#define HID_EP_IN 0x81 -#define HID_EP_SZ 0x10 +#define HID_EP_IN 0x81 +#define HID_EP_OUT 0x01 +#define HID_EP_SZ 0x10 #define HID_INTERVAL 2 @@ -16,6 +17,7 @@ struct HidIntfDescriptor { struct usb_interface_descriptor hid; struct usb_hid_descriptor hid_desc; struct usb_endpoint_descriptor hid_ep_in; + struct usb_endpoint_descriptor hid_ep_out; }; struct HidConfigDescriptor { @@ -162,7 +164,7 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .bDescriptorType = USB_DTYPE_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, - .bNumEndpoints = 1, + .bNumEndpoints = 2, .bInterfaceClass = USB_CLASS_HID, .bInterfaceSubClass = USB_HID_SUBCLASS_BOOT, .bInterfaceProtocol = USB_HID_PROTO_KEYBOARD, @@ -187,6 +189,15 @@ static const struct HidConfigDescriptor hid_cfg_desc = { .wMaxPacketSize = HID_EP_SZ, .bInterval = HID_INTERVAL, }, + .hid_ep_out = + { + .bLength = sizeof(struct usb_endpoint_descriptor), + .bDescriptorType = USB_DTYPE_ENDPOINT, + .bEndpointAddress = HID_EP_OUT, + .bmAttributes = USB_EPTYPE_INTERRUPT, + .wMaxPacketSize = HID_EP_SZ, + .bInterval = HID_INTERVAL, + }, }, }; @@ -481,13 +492,17 @@ static usbd_respond hid_ep_config(usbd_device* dev, uint8_t cfg) { switch(cfg) { case 0: /* deconfiguring device */ + usbd_ep_deconfig(dev, HID_EP_OUT); usbd_ep_deconfig(dev, HID_EP_IN); + usbd_reg_endpoint(dev, HID_EP_OUT, 0); usbd_reg_endpoint(dev, HID_EP_IN, 0); return usbd_ack; case 1: /* configuring device */ usbd_ep_config(dev, HID_EP_IN, USB_EPTYPE_INTERRUPT, HID_EP_SZ); + usbd_ep_config(dev, HID_EP_OUT, USB_EPTYPE_INTERRUPT, HID_EP_SZ); usbd_reg_endpoint(dev, HID_EP_IN, hid_txrx_ep_callback); + usbd_reg_endpoint(dev, HID_EP_OUT, hid_txrx_ep_callback); usbd_ep_write(dev, HID_EP_IN, 0, 0); boot_protocol = false; /* BIOS will SET_PROTOCOL if it wants this */ return usbd_ack;