mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-21 20:42:15 -07:00
Merge remote-tracking branch 'mntm/dev' into kiisu-mntm
This commit is contained in:
+9
-2
@@ -1,4 +1,8 @@
|
||||
### Added:
|
||||
- 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)
|
||||
@@ -8,15 +12,18 @@
|
||||
- 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)
|
||||
- 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)
|
||||
|
||||
+1
-1
Submodule applications/external updated: 8f67e3700c...80f30dc1dd
@@ -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;
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"],
|
||||
|
||||
@@ -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 <flipper_application/flipper_application.h>
|
||||
#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
|
||||
#include "datetime.h"
|
||||
#include <furi_hal.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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*
|
||||
|
||||
|
Reference in New Issue
Block a user