From e66178ec3b70d10bc5b7b834d0f1bdea62b87227 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Sun, 1 Jun 2025 05:14:40 +0100 Subject: [PATCH 1/8] VGM Tool: Fixed RGB firmware UART regression --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 126649c30..b6c2a8400 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - Portal Of Flipper: Implement auth for the xbox 360 (by @sanjay900) - Quac: Fix link imports not working, fix RAW Sub-GHz files (by @xMasterX & @WillyJL) - Seos Compatible: Add support for reading Seader files that have SIO, Add custom zero key ADF OID (by @bettse) + - VGM Tool: Fixed RGB firmware UART regression (by @WillyJL) - UL: Sub-GHz Playlist: Add support for custom modulation presets, remake with txrx library and support for dynamic signals, cleanup code (by @xMasterX) - OFW: Infrared: Add text scroll to remote buttons (by @956MB) - Sub-GHz: diff --git a/applications/external b/applications/external index 8f67e3700..e28905b4d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 8f67e3700cf805ce00fa6dc7860d67f4ca13a19b +Subproject commit e28905b4d66e9e72a509eaa217b1334030ae787b From 06e91e33722b4e1003eb150fbf3884d47d94d558 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Tue, 3 Jun 2025 01:01:40 +0100 Subject: [PATCH 2/8] Update apps --nobuild - ESP Flasher: Bump Marauder 1.6.2 (by @justcallmekoko) - Quac: Add Sub-GHz duration setting (by @rdefeo) - WiFi Marauder: Support for new commands from ESP32Marauder 1.6.x (by @justcallmekoko) --- CHANGELOG.md | 5 +++-- applications/external | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6c2a8400..8780fb25d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,15 @@ - Authenticator: New options to have space between groups of digits (by @akopachov) - Camera Suite: Handle 128x128 image, fix image rotation bug (by @rnadyrshin) - Combo Cracker: Many usability improvements (by @CharlesTheGreat77) - - ESP Flasher: Bump Marauder 1.5.1 (by @justcallmekoko), FlipperHTTP 2.0 (by @jblanked) + - ESP Flasher: Bump Marauder 1.6.2 (by @justcallmekoko), FlipperHTTP 2.0 (by @jblanked) - Flame RNG: New App Icon (by @Kuronons), Improved the RNG using the hardware RNG and some bit mixing (by @OrionW06) - FlipWiFi: Added Deauthentication mode (by @jblanked) - Passy: Capitalize document number (by @bettse) - Picopass: Bugfixes and refactoring (by @bettse) - Portal Of Flipper: Implement auth for the xbox 360 (by @sanjay900) - - Quac: Fix link imports not working, fix RAW Sub-GHz files (by @xMasterX & @WillyJL) + - Quac: Fix link imports not working, fix RAW Sub-GHz files (by @xMasterX & @WillyJL), add Sub-GHz duration setting (by @rdefeo) - Seos Compatible: Add support for reading Seader files that have SIO, Add custom zero key ADF OID (by @bettse) + - WiFi Marauder: Support for new commands from ESP32Marauder 1.6.x (by @justcallmekoko) - VGM Tool: Fixed RGB firmware UART regression (by @WillyJL) - UL: Sub-GHz Playlist: Add support for custom modulation presets, remake with txrx library and support for dynamic signals, cleanup code (by @xMasterX) - OFW: Infrared: Add text scroll to remote buttons (by @956MB) diff --git a/applications/external b/applications/external index e28905b4d..67d90f8b0 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit e28905b4d66e9e72a509eaa217b1334030ae787b +Subproject commit 67d90f8b06febdfeb3cbf537b953fefa14dddbab From 894e5b6e39cab1e3846ad31144fa828e8babfa6e Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Tue, 3 Jun 2025 01:58:13 +0100 Subject: [PATCH 3/8] Apps: Add Sub-GHz Playlist Creator (by coolerUA) --nobuild --- CHANGELOG.md | 2 ++ applications/external | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8780fb25d..a400862ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ ### Added: +- Apps: + - Sub-GHz: Sub-GHz Playlist Creator (by @coolerUA) - UL: Sub-GHz: Add keeloq ironlogic aka il100 smart clone cloners support (by @xMasterX & Vitaly) - UL: iButton: Add TM01x Dallas write support (by @Leptopt1los) - UL: Display: Backlight option "Always ON" (by @Dmitry422) diff --git a/applications/external b/applications/external index 67d90f8b0..6582ac3b6 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 67d90f8b06febdfeb3cbf537b953fefa14dddbab +Subproject commit 6582ac3b6ff8f29f490e92109d24f84c1155dfea From 966cd3925049abbbd2ae96e75f87f8889973d022 Mon Sep 17 00:00:00 2001 From: Thea Juna Schwanke Date: Tue, 3 Jun 2025 03:16:13 +0200 Subject: [PATCH 4/8] RFID: Add DEZ10 representation to EM410X (#418) * Add DEZ10 to EM410X output * make EM410X read success scene text box scrollable so no content overlaps with the buttons * Reduce DEZ10 variable to 32bits as 64 is not needed * run ./fbt format for consistent code formatting * Preview next line like other apps * Update changelog --------- Co-authored-by: WillyJL <49810075+WillyJL@users.noreply.github.com> --- CHANGELOG.md | 1 + applications/main/lfrfid/scenes/lfrfid_scene_read_success.c | 3 +-- lib/lfrfid/protocols/protocol_em4100.c | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a400862ea..b3f430a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - WiFi Marauder: Support for new commands from ESP32Marauder 1.6.x (by @justcallmekoko) - VGM Tool: Fixed RGB firmware UART regression (by @WillyJL) - UL: Sub-GHz Playlist: Add support for custom modulation presets, remake with txrx library and support for dynamic signals, cleanup code (by @xMasterX) +- RFID: Add DEZ10 representation to EM410X (by @realcatgirly) - OFW: Infrared: Add text scroll to remote buttons (by @956MB) - Sub-GHz: - UL: Rename and extend Alarms ignore option with Hollarm & GangQi (by @xMasterX) diff --git a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c index b0e373ea5..354783ff6 100644 --- a/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c +++ b/applications/main/lfrfid/scenes/lfrfid_scene_read_success.c @@ -41,8 +41,7 @@ void lfrfid_scene_read_success_on_enter(void* context) { furi_string_cat_printf(display_text, "\n%s", furi_string_get_cstr(rendered_data)); furi_string_free(rendered_data); - widget_add_text_box_element( - widget, 0, 16, 128, 52, AlignLeft, AlignTop, furi_string_get_cstr(display_text), true); + widget_add_text_scroll_element(widget, 0, 16, 128, 35, furi_string_get_cstr(display_text)); widget_add_button_element(widget, GuiButtonTypeLeft, "Retry", lfrfid_widget_callback, app); widget_add_button_element(widget, GuiButtonTypeRight, "More", lfrfid_widget_callback, app); diff --git a/lib/lfrfid/protocols/protocol_em4100.c b/lib/lfrfid/protocols/protocol_em4100.c index ed18133dc..9ff6724cb 100644 --- a/lib/lfrfid/protocols/protocol_em4100.c +++ b/lib/lfrfid/protocols/protocol_em4100.c @@ -374,11 +374,13 @@ void protocol_em4100_render_data(ProtocolEM4100* protocol, FuriString* result) { furi_string_printf( result, "FC: %03u Card: %05hu CL:%hhu\n" - "DEZ 8: %08lu", + "DEZ 8: %08lu\n" + "DEZ 10: %010lu", data[2], (uint16_t)((data[3] << 8) | (data[4])), protocol->clock_per_bit, - (uint32_t)((data[2] << 16) | (data[3] << 8) | (data[4]))); + (uint32_t)((data[2] << 16) | (data[3] << 8) | (data[4])), + (uint32_t)((data[1] << 24) | (data[2] << 16) | (data[3] << 8) | (data[4]))); } const ProtocolBase protocol_em4100 = { From dc45fb985d25a56c773cc077b9563c2aeaa6f531 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Tue, 3 Jun 2025 02:19:00 +0100 Subject: [PATCH 5/8] ESP Flasher: Bump Marauder 1.6.2 (by justcallmekoko) --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 6582ac3b6..750a4bba0 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 6582ac3b6ff8f29f490e92109d24f84c1155dfea +Subproject commit 750a4bba0e8b6a93bf2fdbefacd4e81a91758464 From 9a4b8128989ba4eef21a8dfe50e37eaea0ebd60d Mon Sep 17 00:00:00 2001 From: hazardousvoltage <104310555+hazardousvoltage@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:23:28 -0500 Subject: [PATCH 6/8] NFC plugin: Ventra ULEV1 parser (#310) * Ventra ULEV1 parser * Ventra fixes * Style * Fix merge * Update changelog --------- Co-authored-by: Willy-JL <49810075+Willy-JL@users.noreply.github.com> Co-authored-by: WillyJL <49810075+WillyJL@users.noreply.github.com> --- CHANGELOG.md | 1 + applications/main/nfc/application.fam | 9 + .../main/nfc/plugins/supported_cards/ventra.c | 292 ++++++++++++++++++ 3 files changed, 302 insertions(+) create mode 100644 applications/main/nfc/plugins/supported_cards/ventra.c diff --git a/CHANGELOG.md b/CHANGELOG.md index b3f430a19..ca4a444b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Added: - Apps: - Sub-GHz: Sub-GHz Playlist Creator (by @coolerUA) +- NFC: Ventra ULEV1 parser (by @hazardousvoltage) - UL: Sub-GHz: Add keeloq ironlogic aka il100 smart clone cloners support (by @xMasterX & Vitaly) - UL: iButton: Add TM01x Dallas write support (by @Leptopt1los) - UL: Display: Backlight option "Always ON" (by @Dmitry422) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 2edf748ce..47522835b 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -520,6 +520,15 @@ App( sources=["plugins/supported_cards/csc.c"], ) +App( + appid="ventra_parser", + apptype=FlipperAppType.PLUGIN, + entry_point="ventra_plugin_ep", + targets=["f7"], + requires=["nfc"], + sources=["plugins/supported_cards/ventra.c"], +) + App( appid="cli_nfc", targets=["f7"], diff --git a/applications/main/nfc/plugins/supported_cards/ventra.c b/applications/main/nfc/plugins/supported_cards/ventra.c new file mode 100644 index 000000000..9abdc1ed7 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/ventra.c @@ -0,0 +1,292 @@ +// Parser for CTA Ventra Ultralight cards +// Made by @hazardousvoltage +// Based on my own research, with... +// Credit to https://www.lenrek.net/experiments/compass-tickets/ & MetroDroid project for underlying info +// +// This parser can decode the paper single-use and single/multi-day paper passes using Ultralight EV1 +// The plastic cards are DESFire and fully locked down, not much useful info extractable +// TODO: +// - Sort the duplicate/rare ticket types +// - Database of stop IDs for trains? Buses there's just too damn many, but you can find them here: +// https://data.cityofchicago.org/Transportation/CTA-Bus-Stops-kml/84eu-buny/about_data +// - Generalize to handle all known Cubic Nextfare Ultralight systems? Anyone wants to send me specimen dumps, hit me up on Discord. + +#include "nfc_supported_card_plugin.h" + +#include +#include +#include "datetime.h" +#include + +#define TAG "Ventra" + +DateTime ventra_exp_date = {0}, ventra_validity_date = {0}; +uint8_t ventra_high_seq = 0, ventra_cur_blk = 0, ventra_mins_active = 0; + +uint32_t time_now() { + return furi_hal_rtc_get_timestamp(); +} + +static DateTime dt_delta(DateTime dt, uint8_t delta_days) { + // returns shifted DateTime, from initial DateTime and time offset in seconds + DateTime dt_shifted = {0}; + datetime_timestamp_to_datetime( + datetime_datetime_to_timestamp(&dt) - (uint64_t)delta_days * 86400, &dt_shifted); + return dt_shifted; +} + +/* +static long dt_diff(DateTime dta, DateTime dtb) { + // returns difference in seconds between two DateTimes + long diff; + diff = datetime_datetime_to_timestamp(&dta) - datetime_datetime_to_timestamp(&dtb); + return diff; +} +*/ + +// Card is expired if: +// - Hard expiration date passed (90 days from purchase, encoded in product record) +// - Soft expiration date passed: +// - For passes, n days after first use +// - For tickets, 2 hours after first use +// Calculating these is dumber than it needs to be, see xact record parser. +bool isExpired(void) { + uint32_t ts_hard_exp = datetime_datetime_to_timestamp(&ventra_exp_date); + uint32_t ts_soft_exp = datetime_datetime_to_timestamp(&ventra_validity_date); + uint32_t ts_now = time_now(); + return (ts_now >= ts_hard_exp || ts_now > ts_soft_exp); +} + +static FuriString* ventra_parse_xact(const MfUltralightData* data, uint8_t blk, bool is_pass) { + FuriString* ventra_xact_str = furi_string_alloc(); + uint16_t ts = data->page[blk].data[0] | data->page[blk].data[1] << 8; + uint8_t tran_type = ts & 0x1F; + ts >>= 5; + uint8_t day = data->page[blk].data[2]; + uint32_t work = data->page[blk + 1].data[0] | data->page[blk + 1].data[1] << 8 | + data->page[blk + 1].data[2] << 16; + uint8_t seq = work & 0x7F; + uint16_t exp = (work >> 7) & 0x7FF; + uint8_t exp_day = data->page[blk + 2].data[0]; + uint16_t locus = data->page[blk + 2].data[1] | data->page[blk + 2].data[2] << 8; + uint8_t line = data->page[blk + 2].data[3]; + + // This computes the block timestamp, based on the card expiration date and delta from it + DateTime dt = dt_delta(ventra_exp_date, day); + dt.hour = (ts & 0x7FF) / 60; + dt.minute = (ts & 0x7FF) % 60; + + // If sequence is 0, block isn't used yet (new card with only one active block, typically the first one. + // Otherwise, the block with higher sequence is the latest transaction, and the other block is prior transaction. + // Not necessarily in that order on the card. We need the latest data to compute validity and pretty-print them + // in reverse chrono. So this mess sets some globals as to which block is current, computes the validity times, etc. + if(seq == 0) { + furi_string_printf(ventra_xact_str, "-- EMPTY --"); + return (ventra_xact_str); + } + if(seq > ventra_high_seq) { + ventra_high_seq = seq; + ventra_cur_blk = blk; + ventra_mins_active = data->page[blk + 1].data[3]; + // Figure out the soft expiration. For passes it's easy, the readers update the "exp" field in the transaction record. + // Tickets, not so much, readers don't update "exp", but each xact record has "minutes since last tap" which is + // updated and carried forward. That, plus transaction timestamp, gives the expiration time. + if(tran_type == 6) { // Furthermore, purchase transactions set bogus expiration dates + if(is_pass) { + ventra_validity_date = dt_delta(ventra_exp_date, exp_day); + ventra_validity_date.hour = (exp & 0x7FF) / 60; + ventra_validity_date.minute = (exp & 0x7FF) % 60; + } else { + uint32_t validity_ts = datetime_datetime_to_timestamp(&dt); + validity_ts += (120 - ventra_mins_active) * 60; + datetime_timestamp_to_datetime(validity_ts, &ventra_validity_date); + } + } + } + + // Type 0 = Purchase, 1 = Train ride, 2 = Bus ride + // TODO: Check PACE and see if it uses a different line code + char linemap[3] = "PTB"; + char* xact_fmt = (line == 2) ? "%c %5d %04d-%02d-%02d %02d:%02d" : + "%c %04X %04d-%02d-%02d %02d:%02d"; + // I like a nice concise display showing all the relevant infos without having to scroll... + // Line StopID DateTime + furi_string_printf( + ventra_xact_str, + xact_fmt, + (line < 3) ? linemap[line] : '?', + locus, + dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute); + return (ventra_xact_str); +} + +static bool ventra_parse(const NfcDevice* device, FuriString* parsed_data) { + furi_assert(device); + furi_assert(parsed_data); + + const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight); + + bool parsed = false; + + do { + // This test can probably be improved -- it matches every Ventra I've seen, but will also match others + // in the same family. Or maybe we just generalize this parser. + if(data->page[4].data[0] != 0x0A || data->page[4].data[1] != 4 || + data->page[4].data[2] != 0 || data->page[6].data[0] != 0 || + data->page[6].data[1] != 0 || data->page[6].data[2] != 0) { + FURI_LOG_D(TAG, "Not Ventra Ultralight"); + break; + } + + // Parse the product record, display interesting data & extract info needed to parse transaction blocks + // Had this in its own function, ended up just setting a bunch of shitty globals, so inlined it instead. + FuriString* ventra_prod_str = furi_string_alloc(); + uint8_t otp = data->page[3].data[0]; + uint8_t prod_code = data->page[5].data[2]; + bool is_pass = false; + switch(prod_code) { + case 2: + case 0x1F: // Only ever seen one of these, it parses like a Single + furi_string_cat_printf(ventra_prod_str, "Single"); + break; + case 3: + case 0x3F: + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "1-Day"); + break; + case 4: // Last I checked, 3 day passes only available at airport TVMs & social service agencies + is_pass = true; + furi_string_cat_printf(ventra_prod_str, "3-Day"); + break; + default: + is_pass = + true; // There are some card types I don't know what they are, but they parse like a pass, not a ticket. + furi_string_cat_printf(ventra_prod_str, "0x%02X", data->page[5].data[2]); + break; + } + + uint16_t date_y = data->page[4].data[3] | (data->page[5].data[0] << 8); + uint8_t date_d = date_y & 0x1F; + uint8_t date_m = (date_y >> 5) & 0x0F; + date_y >>= 9; + date_y += 2000; + ventra_exp_date.day = date_d; + ventra_exp_date.month = date_m; + ventra_exp_date.year = date_y; + ventra_validity_date = ventra_exp_date; // Until we know otherwise + + // Parse the transaction blocks. This sets a few sloppy globals, but it's too complex and repetitive to inline. + FuriString* ventra_xact_str1 = ventra_parse_xact(data, 8, is_pass); + FuriString* ventra_xact_str2 = ventra_parse_xact(data, 12, is_pass); + + uint8_t card_state = 1; + uint8_t rides_left = 0; + + char* card_states[5] = {"???", "NEW", "ACT", "USED", "EXP"}; + + if(ventra_high_seq > 1) card_state = 2; + // On "ticket" product, the OTP bits mark off rides used. Bit 0 seems to be unused, the next 3 are set as rides are used. + // Some, not all, readers will set the high bits to 0x7 when a card is tapped after it's expired or depleted. Have not + // seen other combinations, but if we do, we'll make a nice ???. 1-day passes set the OTP bit 1 on first use. 3-day + // passes do not. But we don't really care, since they don't matter on passes, unless you're trying to rollback one. + if(!is_pass) { + switch(otp) { + case 0: + rides_left = 3; + break; + case 2: + card_state = 2; + rides_left = 2; + break; + case 6: + card_state = 2; + rides_left = 1; + break; + case 0x0E: + case 0x7E: + card_state = 3; + rides_left = 0; + break; + default: + card_state = 0; + rides_left = 0; + break; + } + } + if(isExpired()) { + card_state = 4; + rides_left = 0; + } + + furi_string_printf( + parsed_data, + "\e#Ventra %s (%s)\n", + furi_string_get_cstr(ventra_prod_str), + card_states[card_state]); + + furi_string_cat_printf( + parsed_data, + "Exp: %04d-%02d-%02d %02d:%02d\n", + ventra_validity_date.year, + ventra_validity_date.month, + ventra_validity_date.day, + ventra_validity_date.hour, + ventra_validity_date.minute); + + if(rides_left) { + furi_string_cat_printf(parsed_data, "Rides left: %d\n", rides_left); + } + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str1 : ventra_xact_str2)); + + furi_string_cat_printf( + parsed_data, + "%s\n", + furi_string_get_cstr(ventra_cur_blk == 8 ? ventra_xact_str2 : ventra_xact_str1)); + + furi_string_cat_printf( + parsed_data, "TVM ID: %02X%02X\n", data->page[7].data[1], data->page[7].data[0]); + furi_string_cat_printf(parsed_data, "Tx count: %d\n", ventra_high_seq); + furi_string_cat_printf( + parsed_data, + "Hard Expiry: %04d-%02d-%02d", + ventra_exp_date.year, + ventra_exp_date.month, + ventra_exp_date.day); + + furi_string_free(ventra_prod_str); + furi_string_free(ventra_xact_str1); + furi_string_free(ventra_xact_str2); + + parsed = true; + } while(false); + + return parsed; +} + +/* Actual implementation of app<>plugin interface */ +static const NfcSupportedCardsPlugin ventra_plugin = { + .protocol = NfcProtocolMfUltralight, + .verify = NULL, + .read = NULL, + .parse = ventra_parse, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor ventra_plugin_descriptor = { + .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID, + .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION, + .entry_point = &ventra_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* ventra_plugin_ep(void) { + return &ventra_plugin_descriptor; +} From 326c3a13de9cf4d8731ffcc354f23073d61d5678 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Sat, 7 Jun 2025 15:58:04 +0100 Subject: [PATCH 7/8] IR: "Decode only" mode, more intuitive learn scene buttons --- CHANGELOG.md | 1 + applications/main/infrared/infrared_app.c | 1 + applications/main/infrared/infrared_app_i.h | 1 + .../infrared/scenes/infrared_scene_learn.c | 26 ++++++++++++++++--- lib/infrared/worker/infrared_worker.c | 11 +++++++- lib/infrared/worker/infrared_worker.h | 8 ++++++ targets/f7/api_symbols.csv | 1 + 7 files changed, 44 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4a444b3..5f4b666af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Apps: - Sub-GHz: Sub-GHz Playlist Creator (by @coolerUA) - NFC: Ventra ULEV1 parser (by @hazardousvoltage) +- Infrared: "Decode only" mode to ignore RAW signals, make buttons in learn scene more intuitive (by @WillyJL) - UL: Sub-GHz: Add keeloq ironlogic aka il100 smart clone cloners support (by @xMasterX & Vitaly) - UL: iButton: Add TM01x Dallas write support (by @Leptopt1los) - UL: Display: Backlight option "Always ON" (by @Dmitry422) diff --git a/applications/main/infrared/infrared_app.c b/applications/main/infrared/infrared_app.c index 677eb1117..ec180a180 100644 --- a/applications/main/infrared/infrared_app.c +++ b/applications/main/infrared/infrared_app.c @@ -159,6 +159,7 @@ static InfraredApp* infrared_alloc(void) { app_state->is_otg_enabled = false; app_state->is_easy_mode = false; app_state->is_decode_enabled = true; + app_state->is_decode_forced = false; app_state->edit_target = InfraredEditTargetNone; app_state->edit_mode = InfraredEditModeNone; app_state->current_button_index = InfraredButtonIndexNone; diff --git a/applications/main/infrared/infrared_app_i.h b/applications/main/infrared/infrared_app_i.h index 0d9b440a8..38eadfd82 100644 --- a/applications/main/infrared/infrared_app_i.h +++ b/applications/main/infrared/infrared_app_i.h @@ -91,6 +91,7 @@ typedef struct { bool is_otg_enabled; /**< Whether OTG power (external 5V) is enabled. */ bool is_easy_mode; /**< Whether easy learning mode is enabled. */ bool is_decode_enabled; /**< Whether signal decoding is enabled. */ + bool is_decode_forced; /**< Whether signal decoding is forced. */ InfraredEditTarget edit_target : 8; /**< Selected editing target (a remote or a button). */ InfraredEditMode edit_mode : 8; /**< Selected editing operation (rename or delete). */ int32_t current_button_index; /**< Selected button index (move destination). */ diff --git a/applications/main/infrared/scenes/infrared_scene_learn.c b/applications/main/infrared/scenes/infrared_scene_learn.c index 8c6b557b7..d9be78c6c 100644 --- a/applications/main/infrared/scenes/infrared_scene_learn.c +++ b/applications/main/infrared/scenes/infrared_scene_learn.c @@ -146,9 +146,12 @@ void infrared_scene_learn_on_enter(void* context) { } dialog_ex_set_left_button_text( - dialog_ex, infrared->app_state.is_easy_mode ? "Manual" : "Easy"); + dialog_ex, infrared->app_state.is_easy_mode ? "Easy" : "Manual"); dialog_ex_set_right_button_text( - dialog_ex, infrared->app_state.is_decode_enabled ? "RAW" : "Decode"); + dialog_ex, + infrared->app_state.is_decode_forced ? "Decode" : + infrared->app_state.is_decode_enabled ? "Auto" : + "RAW"); dialog_ex_set_context(dialog_ex, context); dialog_ex_set_result_callback(dialog_ex, infrared_scene_learn_dialog_result_callback); @@ -179,11 +182,26 @@ bool infrared_scene_learn_on_event(void* context, SceneManagerEvent event) { consumed = true; } else if(event.event == DialogExResultRight) { // Toggle signal decoding - infrared->app_state.is_decode_enabled = !infrared->app_state.is_decode_enabled; + if(infrared->app_state.is_decode_forced) { + // Decode -> RAW + infrared->app_state.is_decode_enabled = false; + infrared->app_state.is_decode_forced = false; + } else if(infrared->app_state.is_decode_enabled) { + // Auto -> Decode + infrared->app_state.is_decode_forced = true; + } else { + // RAW -> Auto + infrared->app_state.is_decode_enabled = true; + } infrared_worker_rx_enable_signal_decoding( infrared->worker, infrared->app_state.is_decode_enabled); + infrared_worker_rx_force_signal_decoding( + infrared->worker, infrared->app_state.is_decode_forced); dialog_ex_set_right_button_text( - infrared->dialog_ex, infrared->app_state.is_decode_enabled ? "RAW" : "Decode"); + infrared->dialog_ex, + infrared->app_state.is_decode_forced ? "Decode" : + infrared->app_state.is_decode_enabled ? "Auto" : + "RAW"); consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { diff --git a/lib/infrared/worker/infrared_worker.c b/lib/infrared/worker/infrared_worker.c index cc9361ccf..424d53d0a 100644 --- a/lib/infrared/worker/infrared_worker.c +++ b/lib/infrared/worker/infrared_worker.c @@ -79,6 +79,8 @@ struct InfraredWorker { bool overrun; } rx; }; + + bool decode_force; }; typedef struct { @@ -143,7 +145,7 @@ static void if(instance->rx.received_signal_callback) instance->rx.received_signal_callback( instance->rx.received_signal_context, &instance->signal); - } else { + } else if(!instance->decode_force) { /* Skip first timing if it starts from Space */ if((instance->signal.timings_cnt == 0) && !level) { return; @@ -236,6 +238,7 @@ InfraredWorker* infrared_worker_alloc(void) { instance->infrared_encoder = infrared_alloc_encoder(); instance->blink_enable = false; instance->decode_enable = true; + instance->decode_force = false; instance->notification = furi_record_open(RECORD_NOTIFICATION); instance->state = InfraredWorkerStateIdle; @@ -326,6 +329,12 @@ void infrared_worker_rx_enable_signal_decoding(InfraredWorker* instance, bool en instance->decode_enable = enable; } +void infrared_worker_rx_force_signal_decoding(InfraredWorker* instance, bool force) { + furi_check(instance); + + instance->decode_force = force; +} + void infrared_worker_tx_start(InfraredWorker* instance) { furi_check(instance); furi_check(instance->state == InfraredWorkerStateIdle); diff --git a/lib/infrared/worker/infrared_worker.h b/lib/infrared/worker/infrared_worker.h index 2edb19227..efde4a5df 100644 --- a/lib/infrared/worker/infrared_worker.h +++ b/lib/infrared/worker/infrared_worker.h @@ -84,6 +84,14 @@ void infrared_worker_rx_enable_blink_on_receiving(InfraredWorker* instance, bool */ void infrared_worker_rx_enable_signal_decoding(InfraredWorker* instance, bool enable); +/** Force decoding of received infrared signals, will ignore RAW signals. + * + * @param[in] instance - instance of InfraredWorker + * @param[in] enable - true if you want to force decoding + * false otherwise + */ +void infrared_worker_rx_force_signal_decoding(InfraredWorker* instance, bool force); + /** Clarify is received signal either decoded or raw * * @param[in] signal - received signal diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 030d3b8a7..df0b83a92 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2141,6 +2141,7 @@ Function,+,infrared_worker_get_decoded_signal,const InfraredMessage*,const Infra Function,+,infrared_worker_get_raw_signal,void,"const InfraredWorkerSignal*, const uint32_t**, size_t*" Function,+,infrared_worker_rx_enable_blink_on_receiving,void,"InfraredWorker*, _Bool" Function,+,infrared_worker_rx_enable_signal_decoding,void,"InfraredWorker*, _Bool" +Function,+,infrared_worker_rx_force_signal_decoding,void,"InfraredWorker*, _Bool" Function,+,infrared_worker_rx_set_received_signal_callback,void,"InfraredWorker*, InfraredWorkerReceivedSignalCallback, void*" Function,+,infrared_worker_rx_start,void,InfraredWorker* Function,+,infrared_worker_rx_stop,void,InfraredWorker* From 9b0d52634b25ad08295fd88a1241f5ddd2d5f637 Mon Sep 17 00:00:00 2001 From: WillyJL <49810075+WillyJL@users.noreply.github.com> Date: Sat, 7 Jun 2025 16:05:13 +0100 Subject: [PATCH 8/8] Sync apps --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index 750a4bba0..80f30dc1d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 750a4bba0e8b6a93bf2fdbefacd4e81a91758464 +Subproject commit 80f30dc1dd443b9376b7b5e02aef636dc2a2bcee