From 267140b161ea8f59db40baca409ccd21813ec00a Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Fri, 23 Jan 2026 18:59:14 +0700 Subject: [PATCH 001/202] work to home --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 19 +++-- .../main/subghz/helpers/subghz_txrx.c | 5 -- .../subghz/scenes/subghz_scene_transmitter.c | 67 ++++++++++----- applications/main/subghz/views/transmitter.c | 28 ++++--- lib/subghz/protocols/alutech_at_4n.c | 7 +- lib/subghz/protocols/ansonic.c | 5 +- lib/subghz/protocols/beninca_arc.c | 7 +- lib/subghz/protocols/bett.c | 5 +- lib/subghz/protocols/bin_raw.c | 7 +- lib/subghz/protocols/came.c | 7 +- lib/subghz/protocols/came_atomo.c | 7 +- lib/subghz/protocols/came_twee.c | 7 +- lib/subghz/protocols/chamberlain_code.c | 5 +- lib/subghz/protocols/clemsa.c | 5 +- lib/subghz/protocols/dickert_mahs.c | 7 +- lib/subghz/protocols/doitrand.c | 7 +- lib/subghz/protocols/dooya.c | 7 +- lib/subghz/protocols/elplast.c | 7 +- lib/subghz/protocols/faac_slh.c | 7 +- lib/subghz/protocols/feron.c | 7 +- lib/subghz/protocols/gangqi.c | 7 +- lib/subghz/protocols/gate_tx.c | 5 +- lib/subghz/protocols/hay21.c | 7 +- lib/subghz/protocols/hollarm.c | 7 +- lib/subghz/protocols/holtek.c | 7 +- lib/subghz/protocols/holtek_ht12x.c | 7 +- lib/subghz/protocols/honeywell.c | 7 +- lib/subghz/protocols/honeywell_wdb.c | 7 +- lib/subghz/protocols/hormann.c | 9 +-- lib/subghz/protocols/intertechno_v3.c | 7 +- lib/subghz/protocols/keeloq.c | 7 +- lib/subghz/protocols/kinggates_stylo_4k.c | 7 +- lib/subghz/protocols/linear.c | 5 +- lib/subghz/protocols/linear_delta3.c | 5 +- lib/subghz/protocols/magellan.c | 7 +- lib/subghz/protocols/marantec.c | 7 +- lib/subghz/protocols/marantec24.c | 7 +- lib/subghz/protocols/mastercode.c | 5 +- lib/subghz/protocols/megacode.c | 5 +- lib/subghz/protocols/nero_radio.c | 7 +- lib/subghz/protocols/nero_sketch.c | 7 +- lib/subghz/protocols/nice_flo.c | 5 +- lib/subghz/protocols/nice_flor_s.c | 7 +- lib/subghz/protocols/phoenix_v2.c | 7 +- lib/subghz/protocols/power_smart.c | 7 +- lib/subghz/protocols/revers_rb2.c | 7 +- lib/subghz/protocols/roger.c | 7 +- lib/subghz/protocols/secplus_v1.c | 7 +- lib/subghz/protocols/secplus_v2.c | 7 +- lib/subghz/protocols/smc5326.c | 7 +- lib/subghz/protocols/somfy_keytis.c | 7 +- lib/subghz/protocols/somfy_telis.c | 7 +- lib/subghz/protocols/treadmill37.c | 7 +- targets/f7/furi_hal/furi_hal_subghz.c | 81 ++++++++++++------- 54 files changed, 219 insertions(+), 306 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index abca098eb..40cfe0d4a 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -98,6 +98,8 @@ typedef struct { static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL; +bool subghz_device_cc1101_ext_is_async_tx_complete(void); + static bool subghz_device_cc1101_ext_check_init(void) { furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateInit); subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; @@ -794,15 +796,20 @@ 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)); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t)&(TIM17->ARR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | @@ -821,7 +828,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); @@ -835,7 +842,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; @@ -844,7 +851,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb LL_DMA_SetMemoryAddress( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.gpio_tx_buff); - LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t) & (gpio->port->BSRR)); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t)&(gpio->port->BSRR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | @@ -864,7 +871,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.debug_gpio_buff); LL_DMA_SetPeriphAddress( - SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t) & (gpio->port->BSRR)); + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t)&(gpio->port->BSRR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 8abf373f4..ca3f0b300 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -234,7 +234,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"); @@ -244,10 +243,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; diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index ebd69059f..e7514ac92 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -3,7 +3,7 @@ #include #include - +#include #define TAG "SubGhzSceneTransmitter" void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { @@ -72,25 +72,56 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); dolphin_deed(DolphinDeedSubGhzSend); + + // #subghz_one_press_send# - keyword to search changes + // #start insert# + + // TODO change while condition to subghz_devices_is_async_complete_tx(subghz->txrx->radio_device); + // while( + // !(furi_hal_subghz_is_async_tx_complete() && + // subghz_device_cc1101_ext_is_async_tx_complete())) { + // notification_message(subghz->notifications, &sequence_blink_magenta_10); + // } + + while(!furi_hal_subghz_is_async_tx_complete()) { + notification_message(subghz->notifications, &sequence_blink_magenta_10); + } + 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); + } + // #end insert# } 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); - } - return true; + // #subghz_one_press_send# - keyword to search changes + // } 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); + // } + // return true; } else if(event.event == SubGhzCustomEventViewTransmitterBack) { subghz->state_notifications = SubGhzNotificationStateIDLE; scene_manager_search_and_switch_to_previous_scene( diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 9faa703b6..455ab63a5 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -6,6 +6,9 @@ #include +#include +#include + struct SubGhzViewTransmitter { View* view; SubGhzViewTransmitterCallback callback; @@ -155,7 +158,10 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { true); if(can_be_sent) { - if(event->key == InputKeyOk && event->type == InputTypePress) { + // #subghz_one_press_send# - keyword to search changes + bool allow_events = furi_hal_subghz_is_async_tx_complete() && subghz_device_cc1101_ext_is_async_tx_complete (); + // # + if(event->key == InputKeyOk && event->type == InputTypePress && allow_events) { subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); with_view_model( subghz_transmitter->view, @@ -168,10 +174,11 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; - } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; + // #subghz_one_press_send# - keyword to search changes + // } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { + // subghz_transmitter->callback( + // SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + // return true; } // Finish "OK" key processing if(subghz_custom_btn_is_allowed()) { @@ -189,7 +196,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { return true; } - if(event->type == InputTypePress) { + if(event->type == InputTypePress && allow_events) { with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, @@ -209,10 +216,11 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; - } else if(event->type == InputTypeRelease) { - subghz_transmitter->callback( - SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - return true; + // #subghz_one_press_send# - keyword to search changes + // } else if(event->type == InputTypeRelease) { + // subghz_transmitter->callback( + // SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + // return true; } } } diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 8dff70dd1..b442f61c9 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -95,7 +95,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 = 2; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -483,10 +483,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_alutech_at_4n_deserialize( 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; diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 75f803370..7ad0732d6 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -150,10 +150,7 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_ansonic_get_upload(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 62eb925f8..d269aaa47 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -256,7 +256,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 = 2; instance->encoder.size_upload = 800; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -412,10 +412,7 @@ SubGhzProtocolStatus 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; diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 44946a2f6..173671aaa 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -169,10 +169,7 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_bett_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index ca52cdd49..4753d467c 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -142,7 +142,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 = 2; 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)); @@ -309,10 +309,7 @@ SubGhzProtocolStatus res = SubGhzProtocolStatusErrorParserOthers; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 2762a2484..5037f1a4b 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -90,7 +90,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -181,10 +181,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_came_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index f8ec7baa0..60d1e93a5 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -89,7 +89,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 = 2; instance->encoder.size_upload = 900; //actual size 766+ instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -374,10 +374,7 @@ SubGhzProtocolStatus 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; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 3bb909dc8..c2af278ea 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -109,7 +109,7 @@ 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.repeat = 1; instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -254,10 +254,7 @@ SubGhzProtocolStatus if(res != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_came_twee_remote_controller(&instance->generic); subghz_protocol_encoder_came_twee_get_upload(instance); instance->encoder.front = 0; // reset position before start diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index fda224bb6..d01c80c2e 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -223,10 +223,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index 672abcba3..fecbd4dfd 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -168,10 +168,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_clemsa_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index 65be6fd0c..ac00fd28a 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -132,7 +132,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -207,10 +207,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_dickert_mahs_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 7c7946042..ab700f5ad 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -82,7 +82,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -149,10 +149,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_doitrand_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index fd8645a0b..76d2bac60 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -77,7 +77,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -159,10 +159,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_dooya_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c index 18d6d07b4..a9bd29562 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -141,10 +141,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_encoder_elplast_get_upload(instance); instance->encoder.is_running = true; } while(false); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 147e452eb..d0cfc721c 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -109,7 +109,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; @@ -398,10 +398,7 @@ SubGhzProtocolStatus subghz_protocol_faac_slh_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_encoder_faac_slh_get_upload(instance); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index 9591a4ebb..f7d653f4c 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -74,7 +74,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; @@ -160,10 +160,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_feron_check_remote_controller(&instance->generic); subghz_protocol_encoder_feron_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 6b5542410..f78b37aac 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -253,10 +253,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_gangqi_remote_controller(&instance->generic); subghz_protocol_encoder_gangqi_get_upload(instance); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 608567626..08f6f3f41 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -142,10 +142,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index ba6119dae..8a0280168 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -75,7 +75,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -267,10 +267,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_hay21_remote_controller(&instance->generic); subghz_protocol_encoder_hay21_get_upload(instance); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index 9b2a53a05..b03de5b72 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -254,10 +254,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_hollarm_remote_controller(&instance->generic); subghz_protocol_encoder_hollarm_get_upload(instance); diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 7ed5fc152..2735702c5 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -86,7 +86,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -155,10 +155,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_holtek_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index bf5e48adf..bf4724130 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -94,7 +94,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -170,10 +170,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 1e3f231e8..7343648d7 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -98,7 +98,7 @@ void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_honeywell; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 4; + instance->encoder.repeat = 5; instance->encoder.size_upload = 64 * 2 + 10; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -211,10 +211,7 @@ SubGhzProtocolStatus break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_encoder_honeywell_get_upload(instance); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 0b8f63a24..0a2cd53d6 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -88,7 +88,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -156,10 +156,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_honeywell_wdb_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index f74a29fec..1ac9f6b0d 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -80,7 +80,7 @@ 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.repeat = 2; instance->encoder.size_upload = 2048; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -110,7 +110,7 @@ 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 + instance->encoder.repeat = 3; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { //Send start bit @@ -153,10 +153,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_hormann_get_upload(instance)) { instance->encoder.front = 0; // reset position before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 71513051b..85818763b 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -86,7 +86,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; @@ -176,10 +176,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index cd657f23f..39f957e24 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -107,7 +107,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -667,10 +667,7 @@ SubGhzProtocolStatus subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index e23042b55..18fb91651 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -101,7 +101,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; @@ -344,10 +344,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_kinggates_stylo_4k_deserialize( 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; diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index f024316e9..e170c90ee 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -160,10 +160,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_linear_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index c2f527ba8..b9b28c66a 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -164,10 +164,7 @@ SubGhzProtocolStatus subghz_protocol_encoder_linear_delta3_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 55048ca61..ef0c9d2ae 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -78,7 +78,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; @@ -164,10 +164,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_magellan_get_upload(instance)) { instance->encoder.front = 0; // reset before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index 53e4d6895..02d6aa801 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -77,7 +77,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; @@ -213,10 +213,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_marantec_remote_controller(&instance->generic); subghz_protocol_encoder_marantec_get_upload(instance); instance->encoder.front = 0; diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index 6f636e8ab..63d8e7be2 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -155,10 +155,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_marantec24_check_remote_controller(&instance->generic); subghz_protocol_encoder_marantec24_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c index e4fae40b3..5a57d764e 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -168,10 +168,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_mastercode_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 2c4bf10a3..13cc99436 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -188,10 +188,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_megacode_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index da5497feb..18fdfffa8 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -77,7 +77,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; @@ -180,10 +180,7 @@ SubGhzProtocolStatus break; } } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 64a75cfc0..0550cf801 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -76,7 +76,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; @@ -161,10 +161,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index 2e5fa96b5..dc423fe83 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -147,10 +147,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 9085ee431..59cf4d2cc 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -104,7 +104,7 @@ 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.repeat = 1; instance->encoder.size_upload = 2400; //wrong!! upload 186*16 = 2976 - actual size about 1728 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -279,10 +279,7 @@ SubGhzProtocolStatus break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - // flipper_format_read_uint32( + // flipper_format_read_uint32( // flipper_format, "Data", (uint32_t*)&instance->generic.data_2, 1); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 1f2731f54..c70b4cb00 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -77,7 +77,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -322,10 +322,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) { diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 6449f720a..ed237780e 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -85,7 +85,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 = 2; instance->encoder.size_upload = 1024; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -206,10 +206,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_power_smart_remote_controller(&instance->generic); subghz_protocol_encoder_power_smart_get_upload(instance); instance->encoder.front = 0; // reset before start diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 941ff5c56..2e9aee592 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -78,7 +78,7 @@ 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.repeat = 5; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -176,10 +176,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_revers_rb2_remote_controller(&instance->generic); subghz_protocol_encoder_revers_rb2_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 9c33b11ec..30767db82 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -265,10 +265,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_roger_check_remote_controller(&instance->generic); subghz_protocol_encoder_roger_get_upload(instance); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 13af0d302..460ff766e 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -98,7 +98,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -298,10 +298,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_secplus_v1_encode(instance)) { ret = SubGhzProtocolStatusErrorParserOthers; break; diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index ad343968b..bcbf3183a 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -92,7 +92,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; @@ -574,10 +574,7 @@ SubGhzProtocolStatus subghz_protocol_secplus_v2_remote_controller( &instance->generic, instance->secplus_packet_1); subghz_protocol_secplus_v2_encode(instance); - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_secplus_v2_get_upload(instance); + subghz_protocol_encoder_secplus_v2_get_upload(instance); //update data for(size_t i = 0; i < sizeof(uint64_t); i++) { diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 217dbb780..bf006e146 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -101,7 +101,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 = 5; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -178,10 +178,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index c9f6f47bd..c462035c6 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -81,7 +81,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; @@ -412,10 +412,7 @@ SubGhzProtocolStatus break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_encoder_somfy_keytis_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 2be378b7d..a4b756daa 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -82,7 +82,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; @@ -341,10 +341,7 @@ SubGhzProtocolStatus break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_encoder_somfy_telis_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/treadmill37.c b/lib/subghz/protocols/treadmill37.c index e9915c296..9784fe1e3 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -151,10 +151,7 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value - flipper_format_read_uint32( - flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - + subghz_protocol_treadmill37_check_remote_controller(&instance->generic); subghz_protocol_encoder_treadmill37_get_upload(instance); instance->encoder.is_running = true; diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index dc6add277..0f8fa939c 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -684,6 +684,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { 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++; @@ -756,48 +758,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); @@ -805,8 +823,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()) { @@ -820,7 +838,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); + dma_config.PeriphOrM2MSrcAddress = (uint32_t)&(gpio->port->BSRR); dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_CIRCULAR; dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; @@ -841,14 +859,15 @@ 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; } bool furi_hal_subghz_is_async_tx_complete(void) { return (furi_hal_subghz.state == SubGhzStateAsyncTx) && (LL_TIM_GetAutoReload(TIM2) == 0); + FURI_LOG_I(TAG, "SubGhzStateAsyncTx %d , TIM2-ARR %ld",furi_hal_subghz.state,LL_TIM_GetAutoReload(TIM2)); } void furi_hal_subghz_stop_async_tx(void) { From 09076229c98c4d91e094c1afee1034c7d30aeee0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 24 Jan 2026 17:59:10 +0300 Subject: [PATCH 002/202] NFC: Fix sending 32+ byte ISO 15693-3 commands ofw pr 4333 by WillyJL --- targets/f7/furi_hal/furi_hal_nfc_iso15693.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c index 028c60927..4c8639f08 100644 --- a/targets/f7/furi_hal/furi_hal_nfc_iso15693.c +++ b/targets/f7/furi_hal/furi_hal_nfc_iso15693.c @@ -8,6 +8,7 @@ #define FURI_HAL_NFC_ISO15693_MAX_FRAME_SIZE (1024U) #define FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE (64) +#define FURI_HAL_NFC_ISO15693_BIT_LEN (4) #define FURI_HAL_NFC_ISO15693_RESP_SOF_SIZE (5) #define FURI_HAL_NFC_ISO15693_RESP_EOF_SIZE (5) @@ -34,9 +35,9 @@ typedef struct { typedef struct { // 4 bits per data bit on transmit - uint8_t fifo_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 4]; + uint8_t fifo_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * FURI_HAL_NFC_ISO15693_BIT_LEN]; size_t fifo_buf_bits; - uint8_t frame_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * 2]; + uint8_t frame_buf[FURI_HAL_NFC_ISO15693_POLLER_MAX_BUFFER_SIZE * FURI_HAL_NFC_ISO15693_BIT_LEN]; size_t frame_buf_bits; } FuriHalNfcIso15693Poller; From 1bbdfc8b09fa75e2f5cdd52b579d65c2b886219f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 24 Jan 2026 18:00:18 +0300 Subject: [PATCH 003/202] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fdd5c190..44063cb06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * Apps: Build tag (**22jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * UI: Various small changes +* OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) * NFC: Fix LED not blinking at SLIX unlock (closes issue #945) * SubGHz: Replaced Cars ignore option with Revers RB2 protocol ignore option * SubGHz: Moved Starline, ScherKhan, Kia decoders into external app From 9a5ec93e9ce753dbd4f31c2db693d3a679dbfd7b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 25 Jan 2026 06:19:20 +0300 Subject: [PATCH 004/202] subghz: fix beninca programming mode --- lib/subghz/protocols/beninca_arc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 62eb925f8..83db66306 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -96,7 +96,7 @@ static uint8_t subghz_protocol_beninca_arc_get_btn_code(void) { case 0x04: btn = 0x02; break; - case 0xFF: + case 0x00: btn = 0x04; break; @@ -106,12 +106,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; From c5ea6f79820ca5b712ade6a21eaa64dcd9fab122 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 25 Jan 2026 06:19:57 +0300 Subject: [PATCH 005/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44063cb06..ff834aadf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Main changes - Current API: 87.4 * SubGHz: **Cardin S449 protocol full support** (64bit keeloq) (with Add manually, and all button codes) (**use FSK12K modulation to read the remote**) (closes issues #735 #908) (by @xMasterX and @zero-mega (thanks!)) -* SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 2 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) +* SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) * SubGHz: **New modulation FSK with 12KHz deviation** * SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder From 8a85ab88a767ab2777f171de2e45b47195f35b0a Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 25 Jan 2026 22:38:11 +0700 Subject: [PATCH 006/202] Home to work --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 2 - .../scenes/subghz_scene_receiver_info.c | 8 ++ .../subghz/scenes/subghz_scene_transmitter.c | 113 +++++++++++------- applications/main/subghz/views/transmitter.c | 28 ++--- 4 files changed, 85 insertions(+), 66 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index 40cfe0d4a..adcf785f2 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -98,8 +98,6 @@ typedef struct { static SubGhzDeviceCC1101Ext* subghz_device_cc1101_ext = NULL; -bool subghz_device_cc1101_ext_is_async_tx_complete(void); - static bool subghz_device_cc1101_ext_check_init(void) { furi_assert(subghz_device_cc1101_ext->state == SubGhzDeviceCC1101ExtStateInit); subghz_device_cc1101_ext->state = SubGhzDeviceCC1101ExtStateIdle; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index e68b0203d..a34239f6b 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -2,6 +2,8 @@ #include +#include "applications/main/subghz/helpers/subghz_txrx_i.h" + #define TAG "SubGhzSceneReceiverInfo" void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { @@ -138,6 +140,12 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX + // #subghz_one_press_send# - keyword to search changes + // when user release OK button we wait until full data packed will send and later stop TX + while(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + notification_message(subghz->notifications, &sequence_blink_magenta_10); + } + //# subghz->state_notifications = SubGhzNotificationStateIDLE; widget_reset(subghz->widget); diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index e7514ac92..65d0c7bc6 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -4,8 +4,13 @@ #include #include + +#include "applications/main/subghz/helpers/subghz_txrx_i.h" + #define TAG "SubGhzSceneTransmitter" +static bool tx_stop_called = false; + void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -66,26 +71,63 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventViewTransmitterSendStart) { + // if we recieve event to start transmission (user press OK button) then start/restart TX subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); dolphin_deed(DolphinDeedSubGhzSend); - - // #subghz_one_press_send# - keyword to search changes - // #start insert# - - // TODO change while condition to subghz_devices_is_async_complete_tx(subghz->txrx->radio_device); - // while( - // !(furi_hal_subghz_is_async_tx_complete() && - // subghz_device_cc1101_ext_is_async_tx_complete())) { - // notification_message(subghz->notifications, &sequence_blink_magenta_10); - // } - - while(!furi_hal_subghz_is_async_tx_complete()) { - notification_message(subghz->notifications, &sequence_blink_magenta_10); - } + } + return true; + } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { + // we recieve event to stop tranmission (user release OK button) but + // hardware TX still working now then set flag to stop it after hardware TX will be realy ended + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + tx_stop_called = true; + return true; + } + // if hardware TX not working now so just stop TX correctly + 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); + } + return true; + } else if(event.event == SubGhzCustomEventViewTransmitterBack) { + // if user press back button then force stop TX if they was active + if(subghz->state_notifications == SubGhzNotificationStateTx) { + subghz_txrx_stop(subghz->txrx); + } + subghz->state_notifications = SubGhzNotificationStateIDLE; + scene_manager_search_and_switch_to_previous_scene( + subghz->scene_manager, SubGhzSceneStart); + return true; + } else if(event.event == SubGhzCustomEventViewTransmitterError) { + furi_string_set(subghz->error_str, "Protocol not\nfound!"); + scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); + } + } else if(event.type == SceneManagerEventTypeTick) { + if(subghz->state_notifications == SubGhzNotificationStateTx) { + // if hardware TX still working at this time so we just blink led and do nothing + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + notification_message(subghz->notifications, &sequence_blink_magenta_10); + return true; + } + // if hardware TX not working now and tx_stop_called = true + // (mean user release OK button early than hardware TX was ended) then we stop TX + if(tx_stop_called) { + tx_stop_called = false; subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_txrx_stop(subghz->txrx); if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) { @@ -101,39 +143,18 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz_txrx_stop(subghz->txrx); furi_hal_subghz_set_rolling_counter_mult(tmp_counter); } - // #end insert# + return true; + } else { + // if state_notifications == SubGhzNotificationStateTx but hardware TX was ended + // and user still dont release OK button then we repeat transmission + subghz->state_notifications = SubGhzNotificationStateIDLE; + if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { + subghz->state_notifications = SubGhzNotificationStateTx; + subghz_scene_transmitter_update_data_show(subghz); + dolphin_deed(DolphinDeedSubGhzSend); + } + return true; } - return true; - // #subghz_one_press_send# - keyword to search changes - // } 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); - // } - // 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; - } else if(event.event == SubGhzCustomEventViewTransmitterError) { - furi_string_set(subghz->error_str, "Protocol not\nfound!"); - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowErrorSub); - } - } else if(event.type == SceneManagerEventTypeTick) { - if(subghz->state_notifications == SubGhzNotificationStateTx) { - notification_message(subghz->notifications, &sequence_blink_magenta_10); } return true; } diff --git a/applications/main/subghz/views/transmitter.c b/applications/main/subghz/views/transmitter.c index 455ab63a5..9faa703b6 100644 --- a/applications/main/subghz/views/transmitter.c +++ b/applications/main/subghz/views/transmitter.c @@ -6,9 +6,6 @@ #include -#include -#include - struct SubGhzViewTransmitter { View* view; SubGhzViewTransmitterCallback callback; @@ -158,10 +155,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { true); if(can_be_sent) { - // #subghz_one_press_send# - keyword to search changes - bool allow_events = furi_hal_subghz_is_async_tx_complete() && subghz_device_cc1101_ext_is_async_tx_complete (); - // # - if(event->key == InputKeyOk && event->type == InputTypePress && allow_events) { + if(event->key == InputKeyOk && event->type == InputTypePress) { subghz_custom_btn_set(SUBGHZ_CUSTOM_BTN_OK); with_view_model( subghz_transmitter->view, @@ -174,11 +168,10 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; - // #subghz_one_press_send# - keyword to search changes - // } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { - // subghz_transmitter->callback( - // SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - // return true; + } else if(event->key == InputKeyOk && event->type == InputTypeRelease) { + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + return true; } // Finish "OK" key processing if(subghz_custom_btn_is_allowed()) { @@ -196,7 +189,7 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { return true; } - if(event->type == InputTypePress && allow_events) { + if(event->type == InputTypePress) { with_view_model( subghz_transmitter->view, SubGhzViewTransmitterModel * model, @@ -216,11 +209,10 @@ bool subghz_view_transmitter_input(InputEvent* event, void* context) { subghz_transmitter->callback( SubGhzCustomEventViewTransmitterSendStart, subghz_transmitter->context); return true; - // #subghz_one_press_send# - keyword to search changes - // } else if(event->type == InputTypeRelease) { - // subghz_transmitter->callback( - // SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); - // return true; + } else if(event->type == InputTypeRelease) { + subghz_transmitter->callback( + SubGhzCustomEventViewTransmitterSendStop, subghz_transmitter->context); + return true; } } } From 896a50066796e5d4ba1da09073fa65da9d6bb01c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 03:04:46 +0300 Subject: [PATCH 007/202] Fixes to READ_MULTI and GET_BLOCK_SECURITY commands in 15693-3 emu [ci skip] by WillyJL & aaronjamt --- .../presets/nfc/iso15693_signal.c | 6 +++++- .../iso15693_3/iso15693_3_listener.c | 2 -- .../iso15693_3/iso15693_3_listener_i.c | 20 ++++++++++++++++--- .../iso15693_3/iso15693_3_listener_i.h | 12 +++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/lib/digital_signal/presets/nfc/iso15693_signal.c b/lib/digital_signal/presets/nfc/iso15693_signal.c index 43066b5bf..560a393a0 100644 --- a/lib/digital_signal/presets/nfc/iso15693_signal.c +++ b/lib/digital_signal/presets/nfc/iso15693_signal.c @@ -2,8 +2,12 @@ #include +#include + #define BITS_IN_BYTE (8U) +#define ISO15693_SIGNAL_BUFFER_SIZE (ISO15693_3_LISTENER_BUFFER_SIZE * BITS_IN_BYTE + 2) + #define ISO15693_SIGNAL_COEFF_HI (1U) #define ISO15693_SIGNAL_COEFF_LO (4U) @@ -151,7 +155,7 @@ Iso15693Signal* iso15693_signal_alloc(const GpioPin* pin) { Iso15693Signal* instance = malloc(sizeof(Iso15693Signal)); - instance->tx_sequence = digital_sequence_alloc(BITS_IN_BYTE * 255 + 2, pin); + instance->tx_sequence = digital_sequence_alloc(ISO15693_SIGNAL_BUFFER_SIZE, pin); for(uint32_t i = 0; i < Iso15693SignalDataRateNum; ++i) { iso15693_signal_bank_fill(instance, i); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c index 151e4ae4a..2d1d053c3 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener.c @@ -8,8 +8,6 @@ #define TAG "Iso15693_3Listener" -#define ISO15693_3_LISTENER_BUFFER_SIZE (256U) - Iso15693_3Listener* iso15693_3_listener_alloc(Nfc* nfc, Iso15693_3Data* data) { furi_assert(nfc); diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c index 6132fbf47..1e0f65bfd 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.c @@ -263,8 +263,9 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( const uint32_t block_index_start = request->first_block_num; const uint32_t block_index_end = - MIN((block_index_start + request->block_count + 1), + MIN((block_index_start + request->block_count), ((uint32_t)instance->data->system_info.block_count - 1)); + const uint32_t block_count = block_index_end - block_index_start + 1; error = iso15693_3_listener_extension_handler( instance, @@ -273,8 +274,21 @@ static Iso15693_3Error iso15693_3_listener_read_multi_blocks_handler( (uint32_t)block_index_end); if(error != Iso15693_3ErrorNone) break; + const bool include_block_security = (flags & ISO15693_3_REQ_FLAG_T4_OPTION) != 0; + const uint8_t bytes_per_block = + (include_block_security ? 1 : 0) + instance->data->system_info.block_size; + const uint32_t response_data_max = + bit_buffer_get_capacity_bytes(instance->tx_buffer) - 1 - 2; // Flags and CRC + const uint32_t response_blocks_max = response_data_max / bytes_per_block; + if(block_count > response_blocks_max) { + // Tested on SLIX2, if asked for more blocks than supported at once there is no reply + // Let's do the same + error = Iso15693_3ErrorIgnore; + break; + } + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { - if(flags & ISO15693_3_REQ_FLAG_T4_OPTION) { + if(include_block_security) { iso15693_3_append_block_security( instance->data, i, instance->tx_buffer); // Block security (optional) } @@ -341,7 +355,7 @@ static Iso15693_3Error iso15693_3_listener_write_multi_blocks_handler( if(error != Iso15693_3ErrorNone) break; - for(uint32_t i = block_index_start; i < block_count + request->first_block_num; ++i) { + for(uint32_t i = block_index_start; i <= block_index_end; ++i) { const uint8_t* block_data = &request->block_data[block_size * i]; iso15693_3_set_block_data(instance->data, i, block_data, block_size); } diff --git a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h index a9e0822bf..24a2d1cf0 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_listener_i.h @@ -10,6 +10,18 @@ extern "C" { #endif +// Based on GET_BLOCKS_SECURITY, one of the commands with lengthier responses: +// - 1 byte flags +// - 1 byte security status * 256 max block count +// - 2 byte crc +// for a response size of 259 bytes. +// There is also READ_MULTI_BLOCKS which has no explicit limit on requested block count +// and ISO 15693-3 also does not specify a maximum overall response length, so this command could +// theoretically result in a 8195 byte response (1 byte flags + 32 byte block * 256 blocks + 2 byte crc); +// for practicality we use a sufficient buffer for a full GET_BLOCKS_SECURITY and +// limit READ_MULTI_BLOCKS to how many blocks we can fit into that buffer size. +#define ISO15693_3_LISTENER_BUFFER_SIZE (259U) + typedef enum { Iso15693_3ListenerStateReady, Iso15693_3ListenerStateSelected, From f0abf0f1342a9810de1a8d52db326121f231283c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 03:11:25 +0300 Subject: [PATCH 008/202] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff834aadf..a6b678b3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * SubGHz: Add 390MHz, 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) * SubGHz: Fixed button mapping for **FAAC RC/XT** * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) +* NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) * Apps: Build tag (**22jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From 209ae6f76f0901847b7a674addcae35e38ed811a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 03:11:50 +0300 Subject: [PATCH 009/202] bump apps tag --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6b678b3d..f5993e7c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**22jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**26jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * UI: Various small changes * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) From 3cc57f7b3e2a46d2e599091b079a65f8e938b417 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 26 Jan 2026 13:07:19 +0700 Subject: [PATCH 010/202] Subghz send by one touch --- .../scenes/subghz_scene_receiver_info.c | 67 +++++++++++++++++-- .../subghz/scenes/subghz_scene_transmitter.c | 6 +- 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index a34239f6b..26b4d4600 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -6,6 +6,8 @@ #define TAG "SubGhzSceneReceiverInfo" +static bool tx_stop_called = false; + void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -140,14 +142,17 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX - // #subghz_one_press_send# - keyword to search changes - // when user release OK button we wait until full data packed will send and later stop TX - while(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - notification_message(subghz->notifications, &sequence_blink_magenta_10); + + // if we recieve event to stop tranmission (user release OK button) but + // hardware TX still working now then set flag to stop it after hardware TX will be realy ended + // else stop TX correctly and start RX + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + tx_stop_called = true; + return true; } - //# subghz->state_notifications = SubGhzNotificationStateIDLE; + //update screen data widget_reset(subghz->widget); subghz_scene_receiver_info_draw_widget(subghz); @@ -187,7 +192,57 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } switch(subghz->state_notifications) { case SubGhzNotificationStateTx: - notification_message(subghz->notifications, &sequence_blink_magenta_10); + // if hardware TX still working at this time so we just blink led and do nothing + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + notification_message(subghz->notifications, &sequence_blink_magenta_10); + return true; + } + // if hardware TX not working now and tx_stop_called = true + // (mean user release OK button early than hardware TX was ended) then we stop TX + if(tx_stop_called) { + tx_stop_called = false; + subghz->state_notifications = SubGhzNotificationStateIDLE; + + //update screen data + widget_reset(subghz->widget); + subghz_scene_receiver_info_draw_widget(subghz); + + subghz_txrx_stop(subghz->txrx); + + // Start RX + 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_get_text_space_left(subghz->history, NULL)) { + subghz->state_notifications = SubGhzNotificationStateRx; + } + } + return true; + } else { + // if current state == SubGhzNotificationStateTx but hardware TX was ended + // and user still not release OK button then we repeat TX + + subghz->state_notifications = SubGhzNotificationStateIDLE; + + //update screen data + widget_reset(subghz->widget); + subghz_scene_receiver_info_draw_widget(subghz); + + //CC1101 Stop RX -> Start TX + subghz_txrx_hopper_pause(subghz->txrx); + 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; + } + + return true; + } break; case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index 65d0c7bc6..bfb974417 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -81,7 +81,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { - // we recieve event to stop tranmission (user release OK button) but + // if we recieve event to stop tranmission (user release OK button) but // hardware TX still working now then set flag to stop it after hardware TX will be realy ended if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { tx_stop_called = true; @@ -145,8 +145,8 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else { - // if state_notifications == SubGhzNotificationStateTx but hardware TX was ended - // and user still dont release OK button then we repeat transmission + // if current state == SubGhzNotificationStateTx but hardware TX was ended + // and user still not release OK button then we repeat transmission subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { subghz->state_notifications = SubGhzNotificationStateTx; From 271c65a969e20a7194b716b23d19fc23a9ce3fa0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 11:51:28 +0300 Subject: [PATCH 011/202] subghz: add jarolift protocol and various fixes --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/helpers/subghz_gen_info.c | 9 + .../main/subghz/helpers/subghz_gen_info.h | 6 + .../helpers/subghz_txrx_create_protocol_key.c | 30 + .../helpers/subghz_txrx_create_protocol_key.h | 8 + .../resources/subghz/assets/keeloq_mfcodes | 137 +-- .../subghz/scenes/subghz_scene_set_button.c | 5 + .../subghz/scenes/subghz_scene_set_counter.c | 16 + .../subghz/scenes/subghz_scene_set_seed.c | 2 + .../subghz/scenes/subghz_scene_set_serial.c | 8 + .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/alutech_at_4n.c | 13 +- lib/subghz/protocols/jarolift.c | 777 ++++++++++++++++++ lib/subghz/protocols/jarolift.h | 108 +++ lib/subghz/protocols/keeloq_common.h | 2 + lib/subghz/protocols/kinggates_stylo_4k.c | 8 +- lib/subghz/protocols/protocol_items.c | 2 +- lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/public_api.h | 18 + targets/f7/api_symbols.csv | 1 + 20 files changed, 1081 insertions(+), 82 deletions(-) create mode 100644 lib/subghz/protocols/jarolift.c create mode 100644 lib/subghz/protocols/jarolift.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 4fb40f5c0..011b53025 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -72,6 +72,7 @@ typedef enum { SetTypeSomfyTelis, SetTypeKingGatesStylo4k, SetTypeBenincaARC, + SetTypeJarolift, SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index a0f07a1d8..bfa609e32 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -541,6 +541,15 @@ 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 SetTypeMotorline433: 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..a680b7ba5 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -12,6 +12,7 @@ typedef enum { GenSomfyTelis, GenKingGatesStylo4k, GenBenincaARC, + GenJarolift, GenNiceFlorS, GenSecPlus1, GenSecPlus2, @@ -73,6 +74,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; 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 075720dfc..27fb71f5b 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -395,6 +395,36 @@ 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, 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_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..285770975 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -124,6 +124,14 @@ 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_came_atomo_protocol( void* context, const char* preset_name, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index b55548844..ae6315a2d 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,71 +1,72 @@ 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: 6E 6F 74 69 63 65 73 20 62 75 6C 67 65 4F 77 4F +212F687B2D38E6E9066E95894E455A6AD3A8861CDDAFD09DBC2557506676332D +2CFF25E9743E4588820D24998B5047E8019C6E200922BCB66830C7F722822A79 +74AA0EF345C27F583042C4EDDBB83D5446387D4E5B31DFD3F7B9D09F0662A0AA95BEB40BF37678954CAE0200B2898D22 +6998443F71D17938425CD231B8500E769976601315D79D673AA37E4F9BDCE50B +3335691566FEDDDE5828BE0A5BBBD9AC013987A1134E68020D3F0F477E09BC71 +1B3A3CBB3D56783220BEAECEA15FFF5CD92AF44F63547F6F84F4533D2B3D820B +A1ECFE74E714D0E1514D0C18903598E7FB7E7AF5C0165DEDE85DFAE05B26C0EC +BA3F2EFBE7CB6671206EA90DA903FF48CCABEC9F393D55B9DB3B7E207A48C059 +CA91805C8ADF02501B41D6BF0C69D7C3DD6E218D4B4EB6BED2FDE1367256E544 +5465E78D877F2594AF40115723C8AC71D09F60ACCC8121CD3B6BA271BEA43B5C +BB1D3DE0A048694FC86CA8E79BB7BFBB1EF91515C44E917FD5017676EE5C4B13 +48C08D8B2B42D025D2557CC650EDA3C5CC301CD71169CFE0044C07A95224109B +3DAE6C5B94C237FF10CF82FAA58517DB4DD4E4D82FD81CFAAA4836C9B8460CE1 +645F8F5D213889BAB0F437497856A0CF402E3D4A3679258BB0BF2B768D1F1714 +AFC3C462698C795B1A407F27F499CD1757931BD676BA43E4B41E8EF2672EC4AE +5A19F0D1CD87C40883D7BE51377CDAD94C977B4F6CFCCECED91D56867C9EC211 +716645BD4F5B18002417A92D6A2BD29D18087F7ADB549A90A2661EB4163EF190 +54B83F7F7F50AD4C3DC3498822240AEB7D9A43728704F14691A39C79FAC9B6A3 +FFDDCEFAFF3ADA3616B388684B1AD4987B83E459BA5037E377FE17CC7EDAA430 +9CC98590A371E580B164A5F1369945A6DB653797331EC898755E84BE93EDF3EF +63697AD15064194A3986CC1F3111A09E3D86AC8D370A8EC4D22EDDA4B9661B63 +F7CE4FA63EA9D388AE4AB6A468768AE91E2E3CF6F1D9A5EA17845466A6801A8C +38B2DE32CF36D68842CB31FD97394892B76FB4E81FCFB33164676BAB354939DE +93192FA1D7E347AF9FC98EE49A8B02869527511D0B263FD3B08CCFE645A39F51 +3D5F832CF75D94B58B078B4EE0DFC1D987795CD49959517F391F9E9F09CA187C +6CBCCCE0F8E6A833D5CA18E09E821A7F93B9FD7D912632E277B1721985F4B701 +5896050E9F4950CD7061ED4DD3BA9FC6013CA52F5C1B0493110FA3934116AC66 +F0D3FF283FBD7CFBFE697DBE752EDD5A1F124DCCAE3B629A146E0E85A7ED4E06 +6B6C7C365A5CABA2F1023ED1F97D182F0B9D575C64393272E19B8B809DF3C329 +1AF30761D85AE1C6DD4D78D4ECE79983C7F58ECE859709570262811CFCA8C97F +FC86201D96F4AE36C63A1C00DA34AC14BFDE317383D19A534362E6149E860366 +54F55F34A27E1D690F56269D29C795105C8407A4E1A9ED431588F559948C115A +4DD62ECD2FE8CBB783E3D7F0B1D95496D585405DE5AEB07C8A4E053B3AAAD808 +EDB356AFA869EFB987F099239449D6B9F1469EA6D8C9F0A6B4171F685E2853A1 +03C058A2C0DB83179E520F035BBAAB01673EFE4FDE717A0F5D1AB6B81BC9D033 +D2F33C08136AB161EED6121E721F8C479CED5F26A1B7DEEDEA8580314C232C5E +51CB3275D6D7250CD88A446242590C3DFD36DF83F5E34379DE73601511CD24B9 +ED4EC928AC505CBEFA6ACB2901C8E2221FC5073624B0B78F8365DD0442EFBA10 +76F959BBF8A4EC4AC22F41F06719BCBA58F070CB65B282BFC2C99F6CC02E30DB +0A44A7C5D4ECF2F08CBDF3FE947D45C6AC586D4558DBFB36719927F58EBB525B +F096777DDC5BF671CBF1A9682EBEE8308AA1002F6CB6899D575A30CFACB30125C8378864C77922360ADD0AC486EFF7F4 +372863D5B6F6D7A671AB734A99403AF230DEBBBA435AB8F236B179960FAFDFC6 +6CC31C5E86CF6851D92FA973253B3B1FC9023B150980A216A63720CD74592EB7 +6643B2CA94415FE4A4E25A3086B8370172C53283360A7BD54B322BD889A5A831 +03209037BD7E96A16C69A4F0B6FEB3F2ABA1408353BE09C53485CB7C2362A490 +BE4619EC34F7FB8DFE53A8ABFDE6A831895B88D0AB1A9B4861BC7EC7F1121611 +41223F7AD8B0F28D08C1EEC2B96324F337F6428759032CAE58A04E8511BC4CBCE497EE1502F18BED5B5038E719F4F085 +1533D9B78F16803DFD8AC0C9C85ECEB6BABE9ADF83BF0B6692857BAA03843444064FCDBC89464D9E002AAFF53C65098B +466CC36F82D1AF9BA0704789B2F8B249E79D4964E7E5CB74917760E7D30AB7FA +E9673C0CF35137C179132DDCA57869E285FE6014BB32C5DD78E7549ED0076906 +A511A9999C777750FA348772A9E2881A75DEC94ED833AC282037B3E10913B150 +F20652B2CA0E0ADD34331D2462CD5AFACC4E01F24EF17C97CFF57089039AD7D8 +AFCB6518894626B9E8AFC7E7D9D0430D1D820969CE79B61B91E63D08D4995308D8403705AE4E25AB0F254D47FB03A1A2 +C43987548689A1DD9A65A3BE76F97A8470263ACAC0696B5882383AE97893B5D6 +7FB47AC9A16094C38D436F9932041A40F6B4644A633361B47C6F6E4A8F2E7C26 +BDD48C4689912872055999ECADCF93D152ACDEC34C6318F2FBBBA1A34C8CC166FCBA31AED11F95C973B21A646828CEEA +1582E265DF46C8A9A019AF338104C267873FD3C2C7AF290263FEF921CC9926E76F37FA31881307C675CF37F8A5A236D7 +790D8AF6D9F5B634F953F5751378BA5412425B950C696549902EB6A9383630D0 +6F44FF0DB55651218C81B41153C9D77B1911C5BFA70650FB9326DD6B8A1479EE +4B0891C1C24E004AB2869A858941C47346EF0789DFAEA1CD1B0E008A73AF2F7E +7E9F990652FD6632612866B6ECA726EAE2112234EBCA59D0C1BF220B94E1F472 +9C08E9DAE977DD30E61E6634F51BFD654330C34E9FB3AA2E8856B428141D7164 +7C0E756C593FFBBE2609F63D142A0D53F99772EB0722616DFC7B203BA70AA5AE +52C03D8984496DFF95503D843CA1980A6CA46AAE3390C6D4D38E277A0937429C185C118B98B6EBE0711F10F47F8D8F3F +DE8A5BFD2AB351BCAD1D7817737946EF2445E3C974C9E11BF7AD058463F0E437 +D09ABB320F59A75C4B2EE8B52E45D52C46226DD8BC8339F53229578733928F1F +C2E6F135E638C83363097EB79ED9A58E94A8E5F626E27083D1363E8BC61B55A4 +F06CC55446D0FCCCE5AD007949AE3FCA9C6FCB3C0CA1B5DE1BA65544B55326F1CF726E1F1E5CA90CA2B986930C9B9D83 diff --git a/applications/main/subghz/scenes/subghz_scene_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index e8914dbc5..d2ef60a91 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_button.c +++ b/applications/main/subghz/scenes/subghz_scene_set_button.c @@ -44,6 +44,10 @@ 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 GenNiceFlorS: byte_ptr = &subghz->gen_info->nice_flor_s.btn; byte_count = sizeof(subghz->gen_info->nice_flor_s.btn); @@ -92,6 +96,7 @@ bool subghz_scene_set_button_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetCounter); diff --git a/applications/main/subghz/scenes/subghz_scene_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index a58749256..8510d4aca 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -50,6 +50,10 @@ 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 GenNiceFlorS: byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.cnt; byte_count = sizeof(subghz->gen_info->nice_flor_s.cnt); @@ -128,6 +132,9 @@ 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 GenNiceFlorS: subghz->gen_info->nice_flor_s.cnt = __bswap16(subghz->gen_info->nice_flor_s.cnt); break; @@ -204,6 +211,15 @@ 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 GenNiceFlorS: generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( subghz->txrx, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index 648cfa1d3..36307f11f 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -32,6 +32,7 @@ void subghz_scene_set_seed_on_enter(void* context) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: @@ -93,6 +94,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index 9219842e5..f30b1a4a5 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_serial.c +++ b/applications/main/subghz/scenes/subghz_scene_set_serial.c @@ -50,6 +50,10 @@ 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 GenNiceFlorS: byte_ptr = (uint8_t*)&subghz->gen_info->nice_flor_s.serial; byte_count = sizeof(subghz->gen_info->nice_flor_s.serial); @@ -122,6 +126,9 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { 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 GenBenincaARC: subghz->gen_info->beninca_arc.serial = __bswap32(subghz->gen_info->beninca_arc.serial); @@ -154,6 +161,7 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: case GenKingGatesStylo4k: case GenBenincaARC: + case GenJarolift: 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..2be40602b 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -22,6 +22,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeKingGatesStylo4k] = "KingGates Stylo4k 433M.", [SetTypeBenincaARC] = "Beninca ARC 433MHz", + [SetTypeJarolift] = "Jarolift 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", @@ -207,6 +208,15 @@ 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 GenNiceFlorS: generated_protocol = subghz_txrx_gen_nice_flor_s_protocol( subghz->txrx, @@ -288,6 +298,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case GenSomfyTelis: // 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 GenNiceFlorS: // Serial (u32), Button (u8), Counter (u16) case GenSecPlus2: // Serial (u32), Button (u8), Counter (u32) case GenPhoenixV2: // Serial (u32), Counter (u16) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 8dff70dd1..687a1e930 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -25,7 +25,6 @@ struct SubGhzProtocolDecoderAlutech_at_4n { SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; - uint64_t data; uint32_t crc; uint16_t header_count; @@ -578,13 +577,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; @@ -617,8 +615,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; @@ -627,7 +625,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; } @@ -640,7 +637,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; @@ -652,7 +649,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; diff --git a/lib/subghz/protocols/jarolift.c b/lib/subghz/protocols/jarolift.c new file mode 100644 index 000000000..9881b9892 --- /dev/null +++ b/lib/subghz/protocols/jarolift.c @@ -0,0 +1,777 @@ +#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 = 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, +}; + +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 = 10; + 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) { + 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(); + + // 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; + } +} + +uint8_t subghz_protocol_decoder_jarolift_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderJarolift* instance = context; + uint8_t hash = 0; + uint8_t* p = (uint8_t*)&instance->generic.data; + for(size_t i = 0; i < 16; 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; + + 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..0f666579e --- /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 + */ +uint8_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_common.h b/lib/subghz/protocols/keeloq_common.h index 90c5ef6d7..1250cd2fc 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -26,6 +26,8 @@ #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 /** * Simple Learning Encrypt diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index e23042b55..9d7313559 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -187,7 +187,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); @@ -220,7 +220,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); @@ -456,7 +456,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; } @@ -476,7 +475,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; @@ -561,7 +559,7 @@ 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))) { diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 2c4054dd2..14d54b8d3 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -27,7 +27,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &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_beninca_arc, &subghz_protocol_jarolift, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index f61e71420..2c36260ed 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -55,3 +55,4 @@ #include "elplast.h" #include "treadmill37.h" #include "beninca_arc.h" +#include "jarolift.h" diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 30b8212ce..7539352dc 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -231,6 +231,24 @@ 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); + typedef struct SubGhzProtocolDecoderBinRAW SubGhzProtocolDecoderBinRAW; void subghz_protocol_decoder_bin_raw_data_input_rssi( diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 7e6190628..1ad9bc413 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3725,6 +3725,7 @@ 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_jarolift_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, 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_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_kinggates_stylo_4k_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*" From a8d5743cf64c3026a29c0b517c78f2feb14dc653 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:05:03 +0300 Subject: [PATCH 012/202] upd changelog --- CHANGELOG.md | 3 +++ documentation/SubGHzSupportedSystems.md | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5993e7c3..8a6bc797f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * SubGHz: **Cardin S449 protocol full support** (64bit keeloq) (with Add manually, and all button codes) (**use FSK12K modulation to read the remote**) (closes issues #735 #908) (by @xMasterX and @zero-mega (thanks!)) * SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) +* SubGHz: **Jarolift protocol full support** (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) * SubGHz: **New modulation FSK with 12KHz deviation** * SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder * SubGHz: **Stilmatic - button 9 support** (two buttons hold simulation) (mapped on arrow keys) @@ -20,6 +21,8 @@ * UI: Various small changes * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) * NFC: Fix LED not blinking at SLIX unlock (closes issue #945) +* SubGHz: Fix Alutech AT4N false positives +* SubGHz: Cleanup of extra local variables * SubGHz: Replaced Cars ignore option with Revers RB2 protocol ignore option * SubGHz: Moved Starline, ScherKhan, Kia decoders into external app * SubGHz: Possible Sommer timings fix diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 65e96225b..0cfcb8881 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -64,9 +64,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 From d47bc0232635fd3b2d09225f1960037e8dd53f18 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 26 Jan 2026 16:15:22 +0700 Subject: [PATCH 013/202] Return protocols Repeat option (used by external apps) --- lib/subghz/protocols/alutech_at_4n.c | 5 ++++- lib/subghz/protocols/ansonic.c | 6 +++++- lib/subghz/protocols/beninca_arc.c | 5 ++++- lib/subghz/protocols/bett.c | 6 +++++- lib/subghz/protocols/bin_raw.c | 6 +++++- lib/subghz/protocols/came.c | 6 +++++- lib/subghz/protocols/came_atomo.c | 5 ++++- lib/subghz/protocols/came_twee.c | 6 +++++- lib/subghz/protocols/chamberlain_code.c | 6 +++++- lib/subghz/protocols/clemsa.c | 6 +++++- lib/subghz/protocols/dickert_mahs.c | 6 +++++- lib/subghz/protocols/doitrand.c | 6 +++++- lib/subghz/protocols/dooya.c | 6 +++++- lib/subghz/protocols/elplast.c | 6 +++++- lib/subghz/protocols/faac_slh.c | 5 ++++- lib/subghz/protocols/feron.c | 6 +++++- lib/subghz/protocols/gangqi.c | 6 +++++- lib/subghz/protocols/gate_tx.c | 6 +++++- lib/subghz/protocols/hay21.c | 6 +++++- lib/subghz/protocols/hollarm.c | 6 +++++- lib/subghz/protocols/holtek.c | 6 +++++- lib/subghz/protocols/holtek_ht12x.c | 6 +++++- lib/subghz/protocols/honeywell.c | 5 ++++- lib/subghz/protocols/honeywell_wdb.c | 6 +++++- lib/subghz/protocols/hormann.c | 6 +++++- lib/subghz/protocols/intertechno_v3.c | 6 +++++- lib/subghz/protocols/keeloq.c | 5 ++++- lib/subghz/protocols/kinggates_stylo_4k.c | 5 ++++- lib/subghz/protocols/legrand.c | 1 + lib/subghz/protocols/linear.c | 6 +++++- lib/subghz/protocols/linear_delta3.c | 6 +++++- lib/subghz/protocols/magellan.c | 6 +++++- lib/subghz/protocols/marantec.c | 6 +++++- lib/subghz/protocols/marantec24.c | 6 +++++- lib/subghz/protocols/mastercode.c | 6 +++++- lib/subghz/protocols/megacode.c | 6 +++++- lib/subghz/protocols/nero_radio.c | 6 +++++- lib/subghz/protocols/nero_sketch.c | 6 +++++- lib/subghz/protocols/nice_flo.c | 6 +++++- lib/subghz/protocols/nice_flor_s.c | 6 +++++- lib/subghz/protocols/phoenix_v2.c | 6 +++++- lib/subghz/protocols/power_smart.c | 6 +++++- lib/subghz/protocols/revers_rb2.c | 6 +++++- lib/subghz/protocols/roger.c | 6 +++++- lib/subghz/protocols/secplus_v1.c | 6 +++++- lib/subghz/protocols/secplus_v2.c | 7 ++++++- lib/subghz/protocols/smc5326.c | 6 +++++- lib/subghz/protocols/somfy_keytis.c | 5 ++++- lib/subghz/protocols/somfy_telis.c | 5 ++++- lib/subghz/protocols/treadmill37.c | 6 +++++- 50 files changed, 238 insertions(+), 49 deletions(-) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 421a57b6a..71c8c4fb4 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -482,7 +482,10 @@ SubGhzProtocolStatus subghz_protocol_encoder_alutech_at_4n_deserialize( 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; diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 7ad0732d6..176ebe0bc 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -150,7 +150,11 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_ansonic_get_upload(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 210b8e0ad..1c2eb471e 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -412,7 +412,10 @@ SubGhzProtocolStatus 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; diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 173671aaa..277d75bd0 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -169,7 +169,11 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_bett_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 4753d467c..8feb403ac 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -309,7 +309,11 @@ SubGhzProtocolStatus res = SubGhzProtocolStatusErrorParserOthers; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_bin_raw_get_upload(instance)) { res = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 5037f1a4b..7be07a476 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -181,7 +181,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_came_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 60d1e93a5..c2f1afe9c 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -374,7 +374,10 @@ SubGhzProtocolStatus 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; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index c2af278ea..1eb28d94f 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -254,7 +254,11 @@ SubGhzProtocolStatus if(res != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_came_twee_remote_controller(&instance->generic); subghz_protocol_encoder_came_twee_get_upload(instance); instance->encoder.front = 0; // reset position before start diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index d01c80c2e..61ef7747b 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -223,7 +223,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_chamb_code_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index fecbd4dfd..3d3ebe3d9 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -168,7 +168,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_clemsa_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index ac00fd28a..f5d710c79 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -207,7 +207,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_dickert_mahs_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index ab700f5ad..158f53169 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -149,7 +149,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_doitrand_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index 76d2bac60..b290da8e1 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -159,7 +159,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_dooya_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c index a9bd29562..f95c937db 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -141,7 +141,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_elplast_get_upload(instance); instance->encoder.is_running = true; } while(false); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index d0cfc721c..03e61d6f2 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -398,7 +398,10 @@ SubGhzProtocolStatus subghz_protocol_faac_slh_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_faac_slh_get_upload(instance); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index f7d653f4c..d02258f04 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -160,7 +160,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_feron_check_remote_controller(&instance->generic); subghz_protocol_encoder_feron_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index f78b37aac..0cdb442d5 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -253,7 +253,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_gangqi_remote_controller(&instance->generic); subghz_protocol_encoder_gangqi_get_upload(instance); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 08f6f3f41..8572edd58 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -142,7 +142,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_gate_tx_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 8a0280168..701e98659 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -267,7 +267,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_hay21_remote_controller(&instance->generic); subghz_protocol_encoder_hay21_get_upload(instance); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index b03de5b72..da0dc97e1 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -254,7 +254,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_hollarm_remote_controller(&instance->generic); subghz_protocol_encoder_hollarm_get_upload(instance); diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 2735702c5..4afe25070 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -155,7 +155,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_holtek_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index bf4724130..595237a1f 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -170,7 +170,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_holtek_th12x_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index 7343648d7..d8ce0d8eb 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -211,7 +211,10 @@ SubGhzProtocolStatus break; } - + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_honeywell_get_upload(instance); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 0a2cd53d6..96f5f5e7f 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -156,7 +156,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_honeywell_wdb_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_honeywell_wdb_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 1ac9f6b0d..0d75c0c5f 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -153,7 +153,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_hormann_get_upload(instance)) { instance->encoder.front = 0; // reset position before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 85818763b..7b9bfbf1a 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -176,7 +176,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( ret = SubGhzProtocolStatusErrorValueBitCount; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_intertechno_v3_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 39f957e24..d5a8a9aa7 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -667,7 +667,10 @@ SubGhzProtocolStatus subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); - + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_keeloq_get_upload(instance, instance->generic.btn)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index e6e9d6d38..6b8d0e428 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -344,7 +344,10 @@ SubGhzProtocolStatus subghz_protocol_encoder_kinggates_stylo_4k_deserialize( 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; diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 94a45694c..45a46847c 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -155,6 +155,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } + // optional parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index e170c90ee..61a21ec05 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -160,7 +160,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_linear_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index b9b28c66a..515fc43a6 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -164,7 +164,11 @@ SubGhzProtocolStatus subghz_protocol_encoder_linear_delta3_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_linear_delta3_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index ef0c9d2ae..5fc003ab4 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -164,7 +164,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_magellan_get_upload(instance)) { instance->encoder.front = 0; // reset before start ret = SubGhzProtocolStatusErrorEncoderGetUpload; diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index 02d6aa801..d70c4eed2 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -213,7 +213,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_marantec_remote_controller(&instance->generic); subghz_protocol_encoder_marantec_get_upload(instance); instance->encoder.front = 0; diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index 63d8e7be2..fbd16ecbb 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -155,7 +155,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_marantec24_check_remote_controller(&instance->generic); subghz_protocol_encoder_marantec24_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c index 5a57d764e..b0b7f789d 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -168,7 +168,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_mastercode_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 13cc99436..22e57c364 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -188,7 +188,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_megacode_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 18fdfffa8..d5c284693 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -180,7 +180,11 @@ SubGhzProtocolStatus break; } } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_nero_radio_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 0550cf801..02682f4f1 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -161,7 +161,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_nero_sketch_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index dc423fe83..b47de0079 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -147,7 +147,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_nice_flo_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 59cf4d2cc..67755c79f 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -279,7 +279,11 @@ SubGhzProtocolStatus break; } - // flipper_format_read_uint32( + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + // flipper_format_read_uint32( // flipper_format, "Data", (uint32_t*)&instance->generic.data_2, 1); if(!flipper_format_rewind(flipper_format)) { FURI_LOG_E(TAG, "Rewind error"); diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index c70b4cb00..9b97137f9 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -322,7 +322,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); if(!subghz_protocol_encoder_phoenix_v2_get_upload(instance)) { diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index ed237780e..714232561 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -206,7 +206,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_power_smart_remote_controller(&instance->generic); subghz_protocol_encoder_power_smart_get_upload(instance); instance->encoder.front = 0; // reset before start diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 2e9aee592..5e4ca97e8 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -176,7 +176,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_revers_rb2_remote_controller(&instance->generic); subghz_protocol_encoder_revers_rb2_get_upload(instance); instance->encoder.is_running = true; diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 30767db82..62178981c 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -265,7 +265,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_roger_check_remote_controller(&instance->generic); subghz_protocol_encoder_roger_get_upload(instance); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 460ff766e..3d820b318 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -298,7 +298,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_secplus_v1_encode(instance)) { ret = SubGhzProtocolStatusErrorParserOthers; break; diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index bcbf3183a..902422148 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -574,7 +574,12 @@ SubGhzProtocolStatus subghz_protocol_secplus_v2_remote_controller( &instance->generic, instance->secplus_packet_1); subghz_protocol_secplus_v2_encode(instance); - subghz_protocol_encoder_secplus_v2_get_upload(instance); + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + + subghz_protocol_encoder_secplus_v2_get_upload(instance); //update data for(size_t i = 0; i < sizeof(uint64_t); i++) { diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index bf006e146..201fcd9d7 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -178,7 +178,11 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + if(!subghz_protocol_encoder_smc5326_get_upload(instance)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; break; diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index c462035c6..04dba4736 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -412,7 +412,10 @@ SubGhzProtocolStatus break; } - + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_somfy_keytis_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index a4b756daa..4d4b128f1 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -341,7 +341,10 @@ SubGhzProtocolStatus break; } - + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_encoder_somfy_telis_get_upload(instance, instance->generic.btn); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/treadmill37.c b/lib/subghz/protocols/treadmill37.c index 9784fe1e3..8a7badb94 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -151,7 +151,11 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - + + // Optional value + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); + subghz_protocol_treadmill37_check_remote_controller(&instance->generic); subghz_protocol_encoder_treadmill37_get_upload(instance); instance->encoder.is_running = true; From 14abc959cf0fb86d9a2904a5028e3196ff80fe31 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 26 Jan 2026 16:30:32 +0700 Subject: [PATCH 014/202] start working with subghz button editor --- .../scenes/subghz_scene_signal_settings.c | 40 ++++++++++++++++++- lib/subghz/blocks/generic.h | 5 +++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index e74219a28..548516683 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -14,6 +14,7 @@ static uint16_t counter16 = 0x0; static uint8_t byte_count = 0; static uint8_t* byte_ptr = NULL; static FuriString* byte_input_text; +static uint8_t button = 0x0; #define COUNTER_MODE_COUNT 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { @@ -74,6 +75,23 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex 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, + byte_ptr, + byte_count); + view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); + } + // when we click OK on "Edit button" item + if(index == 2) { + furi_string_cat_str(byte_input_text, " button number 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, @@ -130,9 +148,10 @@ 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; + bool button_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 @@ -143,6 +162,8 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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"); @@ -166,6 +187,18 @@ void subghz_scene_signal_settings_on_enter(void* context) { } } + // ### 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); + byte_ptr = (uint8_t*)&button; + byte_count = 1; + } + furi_assert(byte_ptr); furi_assert(byte_count > 0); @@ -196,6 +229,11 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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 !"); + 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_string_free(tmp_text); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 1e72c5a5e..d3f7804dc 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -35,6 +35,11 @@ struct SubGhzBlockGenericGlobal { 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) bool cnt_is_available; // is there counter available for protocol (used in counter editor giu) + + uint8_t current_btn; // global counter value; + uint8_t new_btn; // global counter value; + bool btn_need_override; // flag for protocols to override signals counter inside of protocols + bool btn_is_available; // is there counter available for protocol (used in counter editor giu) }; extern SubGhzBlockGenericGlobal subghz_block_generic_global; //global structure for subghz From eb91b7a974f2ee6930635dfae6bbc67996d6ee1f Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 26 Jan 2026 16:41:57 +0700 Subject: [PATCH 015/202] Protocols repeat correction --- lib/subghz/protocols/alutech_at_4n.c | 2 +- lib/subghz/protocols/beninca_arc.c | 2 +- lib/subghz/protocols/bin_raw.c | 2 +- lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/hormann.c | 2 +- lib/subghz/protocols/jarolift.c | 2 +- lib/subghz/protocols/power_smart.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 71c8c4fb4..21ad71f01 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -94,7 +94,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 = 2; + instance->encoder.repeat = 3; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 1c2eb471e..786de5fa8 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -256,7 +256,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 = 2; + instance->encoder.repeat = 1; instance->encoder.size_upload = 800; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 8feb403ac..9ac339d79 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -142,7 +142,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 = 2; + 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)); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index c2f1afe9c..748d15af5 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -89,7 +89,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 = 2; + 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; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 0d75c0c5f..250ab4f62 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -80,7 +80,7 @@ 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 = 2; + instance->encoder.repeat = 3; instance->encoder.size_upload = 2048; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/jarolift.c b/lib/subghz/protocols/jarolift.c index 9881b9892..81eca4fde 100644 --- a/lib/subghz/protocols/jarolift.c +++ b/lib/subghz/protocols/jarolift.c @@ -100,7 +100,7 @@ void* subghz_protocol_encoder_jarolift_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; diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 714232561..1e8f7098c 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -85,7 +85,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 = 2; + instance->encoder.repeat = 3; instance->encoder.size_upload = 1024; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; From 7fa5624c509789adb2ad83a608537710ee68837a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:45:57 +0300 Subject: [PATCH 016/202] Revert "start working with subghz button editor" This reverts commit 14abc959cf0fb86d9a2904a5028e3196ff80fe31. --- .../scenes/subghz_scene_signal_settings.c | 40 +------------------ lib/subghz/blocks/generic.h | 5 --- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 548516683..e74219a28 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -14,7 +14,6 @@ static uint16_t counter16 = 0x0; static uint8_t byte_count = 0; static uint8_t* byte_ptr = NULL; static FuriString* byte_input_text; -static uint8_t button = 0x0; #define COUNTER_MODE_COUNT 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { @@ -75,23 +74,6 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex 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, - byte_ptr, - byte_count); - view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdByteInput); - } - // when we click OK on "Edit button" item - if(index == 2) { - furi_string_cat_str(byte_input_text, " button number 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, @@ -148,10 +130,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; - bool button_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 @@ -162,8 +143,6 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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"); @@ -187,18 +166,6 @@ void subghz_scene_signal_settings_on_enter(void* context) { } } - // ### 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); - byte_ptr = (uint8_t*)&button; - byte_count = 1; - } - furi_assert(byte_ptr); furi_assert(byte_count > 0); @@ -229,11 +196,6 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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 !"); - 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_string_free(tmp_text); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index d3f7804dc..1e72c5a5e 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -35,11 +35,6 @@ struct SubGhzBlockGenericGlobal { 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) bool cnt_is_available; // is there counter available for protocol (used in counter editor giu) - - uint8_t current_btn; // global counter value; - uint8_t new_btn; // global counter value; - bool btn_need_override; // flag for protocols to override signals counter inside of protocols - bool btn_is_available; // is there counter available for protocol (used in counter editor giu) }; extern SubGhzBlockGenericGlobal subghz_block_generic_global; //global structure for subghz From 5564fe1f314f5f0cdf7069b579b6033bae5c1407 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:46:30 +0300 Subject: [PATCH 017/202] fmt --- applications/drivers/subghz/cc1101_ext/cc1101_ext.c | 6 +++--- lib/subghz/protocols/legrand.c | 2 +- targets/f7/furi_hal/furi_hal_subghz.c | 12 ++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index adcf785f2..e24ed8f7a 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -807,7 +807,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb // 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)); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, (uint32_t) & (TIM17->ARR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH3_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | @@ -849,7 +849,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb LL_DMA_SetMemoryAddress( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.gpio_tx_buff); - LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t)&(gpio->port->BSRR)); + LL_DMA_SetPeriphAddress(SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, (uint32_t) & (gpio->port->BSRR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH4_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | @@ -869,7 +869,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t)subghz_device_cc1101_ext->async_tx.debug_gpio_buff); LL_DMA_SetPeriphAddress( - SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t)&(gpio->port->BSRR)); + SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, (uint32_t) & (gpio->port->BSRR)); LL_DMA_ConfigTransfer( SUBGHZ_DEVICE_CC1101_EXT_DMA_CH5_DEF, LL_DMA_DIRECTION_MEMORY_TO_PERIPH | LL_DMA_MODE_CIRCULAR | LL_DMA_PERIPH_NOINCREMENT | diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 45a46847c..7b8b70204 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -155,7 +155,7 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - + // optional parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index 0f8fa939c..b9723487e 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -684,7 +684,7 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { 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. + // 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; @@ -771,7 +771,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* // 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.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 = @@ -838,7 +838,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t)&(gpio->port->BSRR); + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_CIRCULAR; dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; @@ -867,7 +867,11 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* bool furi_hal_subghz_is_async_tx_complete(void) { return (furi_hal_subghz.state == SubGhzStateAsyncTx) && (LL_TIM_GetAutoReload(TIM2) == 0); - FURI_LOG_I(TAG, "SubGhzStateAsyncTx %d , TIM2-ARR %ld",furi_hal_subghz.state,LL_TIM_GetAutoReload(TIM2)); + FURI_LOG_I( + TAG, + "SubGhzStateAsyncTx %d , TIM2-ARR %ld", + furi_hal_subghz.state, + LL_TIM_GetAutoReload(TIM2)); } void furi_hal_subghz_stop_async_tx(void) { From 5c7e48cf2f1e46580be55d278f80b4f0b2a3971f Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 26 Jan 2026 16:56:26 +0700 Subject: [PATCH 018/202] Remove Debug log --- targets/f7/furi_hal/furi_hal_subghz.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index b9723487e..ece8dde2c 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -867,11 +867,6 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* bool furi_hal_subghz_is_async_tx_complete(void) { return (furi_hal_subghz.state == SubGhzStateAsyncTx) && (LL_TIM_GetAutoReload(TIM2) == 0); - FURI_LOG_I( - TAG, - "SubGhzStateAsyncTx %d , TIM2-ARR %ld", - furi_hal_subghz.state, - LL_TIM_GetAutoReload(TIM2)); } void furi_hal_subghz_stop_async_tx(void) { From 6789686e3f17f1cde950a13dbd55cea4d3a5cb16 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 13:08:22 +0300 Subject: [PATCH 019/202] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a6bc797f..edc777ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) * SubGHz: **Jarolift protocol full support** (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) +* SubGHz: **Change key SEND action** (This changes solve problem when user press and release OK button too fast and signal was not fully sent) (PR #949 | by @Dmitry422) * SubGHz: **New modulation FSK with 12KHz deviation** * SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder * SubGHz: **Stilmatic - button 9 support** (two buttons hold simulation) (mapped on arrow keys) From dfb17ab4288d1e316b7346b40c748dbdcd45b4a5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:14:06 +0300 Subject: [PATCH 020/202] Revert "Merge pull request #949 from Dmitry422/dev" This reverts commit ae1abd61393a7e46ee1c7d9c4f35c6dbc4c128eb, reversing changes made to a8d5743cf64c3026a29c0b517c78f2feb14dc653. --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 11 +-- .../main/subghz/helpers/subghz_txrx.c | 5 ++ .../scenes/subghz_scene_receiver_info.c | 65 +--------------- .../subghz/scenes/subghz_scene_transmitter.c | 54 +------------ lib/subghz/protocols/alutech_at_4n.c | 2 +- lib/subghz/protocols/ansonic.c | 1 - lib/subghz/protocols/beninca_arc.c | 2 +- lib/subghz/protocols/bett.c | 1 - lib/subghz/protocols/bin_raw.c | 3 +- lib/subghz/protocols/came.c | 3 +- lib/subghz/protocols/came_atomo.c | 2 +- lib/subghz/protocols/came_twee.c | 3 +- lib/subghz/protocols/chamberlain_code.c | 1 - lib/subghz/protocols/clemsa.c | 1 - lib/subghz/protocols/dickert_mahs.c | 3 +- lib/subghz/protocols/doitrand.c | 3 +- lib/subghz/protocols/dooya.c | 3 +- lib/subghz/protocols/elplast.c | 3 +- lib/subghz/protocols/faac_slh.c | 2 +- lib/subghz/protocols/feron.c | 3 +- lib/subghz/protocols/gangqi.c | 3 +- lib/subghz/protocols/gate_tx.c | 1 - lib/subghz/protocols/hay21.c | 3 +- lib/subghz/protocols/hollarm.c | 3 +- lib/subghz/protocols/holtek.c | 3 +- lib/subghz/protocols/holtek_ht12x.c | 3 +- lib/subghz/protocols/honeywell.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 3 +- lib/subghz/protocols/hormann.c | 5 +- lib/subghz/protocols/intertechno_v3.c | 3 +- lib/subghz/protocols/jarolift.c | 2 +- lib/subghz/protocols/keeloq.c | 2 +- lib/subghz/protocols/kinggates_stylo_4k.c | 2 +- lib/subghz/protocols/legrand.c | 1 - lib/subghz/protocols/linear.c | 1 - lib/subghz/protocols/linear_delta3.c | 1 - lib/subghz/protocols/magellan.c | 3 +- lib/subghz/protocols/marantec.c | 3 +- lib/subghz/protocols/marantec24.c | 3 +- lib/subghz/protocols/mastercode.c | 1 - lib/subghz/protocols/megacode.c | 1 - lib/subghz/protocols/nero_radio.c | 3 +- lib/subghz/protocols/nero_sketch.c | 3 +- lib/subghz/protocols/nice_flo.c | 1 - lib/subghz/protocols/nice_flor_s.c | 3 +- lib/subghz/protocols/phoenix_v2.c | 3 +- lib/subghz/protocols/power_smart.c | 3 +- lib/subghz/protocols/revers_rb2.c | 3 +- lib/subghz/protocols/roger.c | 3 +- lib/subghz/protocols/secplus_v1.c | 3 +- lib/subghz/protocols/secplus_v2.c | 4 +- lib/subghz/protocols/smc5326.c | 3 +- lib/subghz/protocols/somfy_keytis.c | 2 +- lib/subghz/protocols/somfy_telis.c | 2 +- lib/subghz/protocols/treadmill37.c | 3 +- targets/f7/furi_hal/furi_hal_subghz.c | 78 +++++++------------ 56 files changed, 81 insertions(+), 256 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index e24ed8f7a..abca098eb 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -794,17 +794,12 @@ 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 to update timer TIM17 ARR by durations from buffer + // Configure DMA update timer 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)); @@ -826,7 +821,7 @@ bool subghz_device_cc1101_ext_start_async_tx(SubGhzDeviceCC1101ExtCallback callb furi_hal_bus_enable(FuriHalBusTIM17); - // Configure TIM 17 + // Configure TIM // Set the timer resolution to 2 us LL_TIM_SetCounterMode(TIM17, LL_TIM_COUNTERMODE_UP); LL_TIM_SetClockDivision(TIM17, LL_TIM_CLOCKDIVISION_DIV1); @@ -840,7 +835,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 DMA to transfer data from gpio_tx_buff directly to gpio pin using BSSR + // Configure tx gpio dma 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/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index ca3f0b300..8abf373f4 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -234,6 +234,7 @@ 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"); @@ -243,6 +244,10 @@ 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; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 26b4d4600..e68b0203d 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -2,12 +2,8 @@ #include -#include "applications/main/subghz/helpers/subghz_txrx_i.h" - #define TAG "SubGhzSceneReceiverInfo" -static bool tx_stop_called = false; - void subghz_scene_receiver_info_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -142,17 +138,8 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { //CC1101 Stop Tx -> Start RX - - // if we recieve event to stop tranmission (user release OK button) but - // hardware TX still working now then set flag to stop it after hardware TX will be realy ended - // else stop TX correctly and start RX - if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - tx_stop_called = true; - return true; - } subghz->state_notifications = SubGhzNotificationStateIDLE; - //update screen data widget_reset(subghz->widget); subghz_scene_receiver_info_draw_widget(subghz); @@ -192,57 +179,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) } switch(subghz->state_notifications) { case SubGhzNotificationStateTx: - // if hardware TX still working at this time so we just blink led and do nothing - if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - notification_message(subghz->notifications, &sequence_blink_magenta_10); - return true; - } - // if hardware TX not working now and tx_stop_called = true - // (mean user release OK button early than hardware TX was ended) then we stop TX - if(tx_stop_called) { - tx_stop_called = false; - subghz->state_notifications = SubGhzNotificationStateIDLE; - - //update screen data - widget_reset(subghz->widget); - subghz_scene_receiver_info_draw_widget(subghz); - - subghz_txrx_stop(subghz->txrx); - - // Start RX - 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_get_text_space_left(subghz->history, NULL)) { - subghz->state_notifications = SubGhzNotificationStateRx; - } - } - return true; - } else { - // if current state == SubGhzNotificationStateTx but hardware TX was ended - // and user still not release OK button then we repeat TX - - subghz->state_notifications = SubGhzNotificationStateIDLE; - - //update screen data - widget_reset(subghz->widget); - subghz_scene_receiver_info_draw_widget(subghz); - - //CC1101 Stop RX -> Start TX - subghz_txrx_hopper_pause(subghz->txrx); - 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; - } - - return true; - } + notification_message(subghz->notifications, &sequence_blink_magenta_10); break; case SubGhzNotificationStateRx: notification_message(subghz->notifications, &sequence_blink_cyan_10); diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index bfb974417..ebd69059f 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -3,14 +3,9 @@ #include #include -#include - -#include "applications/main/subghz/helpers/subghz_txrx_i.h" #define TAG "SubGhzSceneTransmitter" -static bool tx_stop_called = false; - void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { furi_assert(context); SubGhz* subghz = context; @@ -71,7 +66,6 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { SubGhz* subghz = context; if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventViewTransmitterSendStart) { - // if we recieve event to start transmission (user press OK button) then start/restart TX subghz->state_notifications = SubGhzNotificationStateIDLE; if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { @@ -81,13 +75,6 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { - // if we recieve event to stop tranmission (user release OK button) but - // hardware TX still working now then set flag to stop it after hardware TX will be realy ended - if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - tx_stop_called = true; - return true; - } - // if hardware TX not working now so just stop TX correctly subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_txrx_stop(subghz->txrx); if(subghz_custom_btn_get() != SUBGHZ_CUSTOM_BTN_OK) { @@ -105,10 +92,6 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.event == SubGhzCustomEventViewTransmitterBack) { - // if user press back button then force stop TX if they was active - if(subghz->state_notifications == SubGhzNotificationStateTx) { - subghz_txrx_stop(subghz->txrx); - } subghz->state_notifications = SubGhzNotificationStateIDLE; scene_manager_search_and_switch_to_previous_scene( subghz->scene_manager, SubGhzSceneStart); @@ -119,42 +102,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeTick) { if(subghz->state_notifications == SubGhzNotificationStateTx) { - // if hardware TX still working at this time so we just blink led and do nothing - if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - notification_message(subghz->notifications, &sequence_blink_magenta_10); - return true; - } - // if hardware TX not working now and tx_stop_called = true - // (mean user release OK button early than hardware TX was ended) then we stop TX - if(tx_stop_called) { - tx_stop_called = false; - 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); - } - return true; - } else { - // if current state == SubGhzNotificationStateTx but hardware TX was ended - // and user still not release OK button then we repeat transmission - subghz->state_notifications = SubGhzNotificationStateIDLE; - if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { - subghz->state_notifications = SubGhzNotificationStateTx; - subghz_scene_transmitter_update_data_show(subghz); - dolphin_deed(DolphinDeedSubGhzSend); - } - return true; - } + notification_message(subghz->notifications, &sequence_blink_magenta_10); } return true; } diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 21ad71f01..687a1e930 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -94,7 +94,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 176ebe0bc..75f803370 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -150,7 +150,6 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/beninca_arc.c b/lib/subghz/protocols/beninca_arc.c index 786de5fa8..83db66306 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -256,7 +256,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 = 1; + instance->encoder.repeat = 10; instance->encoder.size_upload = 800; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 277d75bd0..44946a2f6 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -169,7 +169,6 @@ SubGhzProtocolStatus FURI_LOG_E(TAG, "Deserialize error"); break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 9ac339d79..ca52cdd49 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -142,7 +142,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 = 3; + instance->encoder.repeat = 10; 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)); @@ -309,7 +309,6 @@ SubGhzProtocolStatus res = SubGhzProtocolStatusErrorParserOthers; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 7be07a476..2762a2484 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -90,7 +90,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -181,7 +181,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 748d15af5..f8ec7baa0 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -89,7 +89,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 = 1; + instance->encoder.repeat = 10; instance->encoder.size_upload = 900; //actual size 766+ instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 1eb28d94f..3bb909dc8 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -109,7 +109,7 @@ 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 = 1; + instance->encoder.repeat = 10; instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -254,7 +254,6 @@ SubGhzProtocolStatus if(res != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 61ef7747b..fda224bb6 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -223,7 +223,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index 3d3ebe3d9..672abcba3 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -168,7 +168,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/dickert_mahs.c b/lib/subghz/protocols/dickert_mahs.c index f5d710c79..65be6fd0c 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -132,7 +132,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -207,7 +207,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 158f53169..7c7946042 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -82,7 +82,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -149,7 +149,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index b290da8e1..fd8645a0b 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -77,7 +77,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -159,7 +159,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c index f95c937db..18d6d07b4 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -73,7 +73,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -141,7 +141,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 03e61d6f2..147e452eb 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -109,7 +109,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index d02258f04..9591a4ebb 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -74,7 +74,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -160,7 +160,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 0cdb442d5..6b5542410 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -76,7 +76,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -253,7 +253,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 8572edd58..608567626 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -142,7 +142,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 701e98659..ba6119dae 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -75,7 +75,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -267,7 +267,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index da0dc97e1..9b2a53a05 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -76,7 +76,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -254,7 +254,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 4afe25070..7ed5fc152 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -86,7 +86,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -155,7 +155,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 595237a1f..bf5e48adf 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -94,7 +94,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -170,7 +170,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index d8ce0d8eb..1e3f231e8 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -98,7 +98,7 @@ void* subghz_protocol_encoder_honeywell_alloc(SubGhzEnvironment* environment) { instance->base.protocol = &subghz_protocol_honeywell; instance->generic.protocol_name = instance->base.protocol->name; - instance->encoder.repeat = 5; + instance->encoder.repeat = 4; instance->encoder.size_upload = 64 * 2 + 10; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index 96f5f5e7f..0b8f63a24 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -88,7 +88,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -156,7 +156,6 @@ SubGhzProtocolStatus subghz_protocol_encoder_honeywell_wdb_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 250ab4f62..f74a29fec 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -80,7 +80,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 2048; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -110,7 +110,7 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm } else { instance->encoder.size_upload = size_upload; } - instance->encoder.repeat = 3; //original remote does 10 repeats + instance->encoder.repeat = 10; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { //Send start bit @@ -153,7 +153,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 7b9bfbf1a..71513051b 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -86,7 +86,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -176,7 +176,6 @@ SubGhzProtocolStatus subghz_protocol_encoder_intertechno_v3_deserialize( ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/jarolift.c b/lib/subghz/protocols/jarolift.c index 81eca4fde..9881b9892 100644 --- a/lib/subghz/protocols/jarolift.c +++ b/lib/subghz/protocols/jarolift.c @@ -100,7 +100,7 @@ void* subghz_protocol_encoder_jarolift_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->keystore = subghz_environment_get_keystore(environment); - instance->encoder.repeat = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index d5a8a9aa7..cd657f23f 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -107,7 +107,7 @@ 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 = 3; + instance->encoder.repeat = 100; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 6b8d0e428..9d7313559 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -101,7 +101,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 7b8b70204..94a45694c 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -155,7 +155,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - // optional parameter flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 61a21ec05..f024316e9 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -160,7 +160,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index 515fc43a6..c2f527ba8 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -164,7 +164,6 @@ SubGhzProtocolStatus subghz_protocol_encoder_linear_delta3_deserialize( if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index 5fc003ab4..55048ca61 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -78,7 +78,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -164,7 +164,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index d70c4eed2..53e4d6895 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -77,7 +77,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -213,7 +213,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index fbd16ecbb..6f636e8ab 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -73,7 +73,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -155,7 +155,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c index b0b7f789d..e4fae40b3 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -168,7 +168,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index 22e57c364..2c4bf10a3 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -188,7 +188,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index d5c284693..da5497feb 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -77,7 +77,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -180,7 +180,6 @@ SubGhzProtocolStatus break; } } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 02682f4f1..64a75cfc0 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -76,7 +76,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -161,7 +161,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index b47de0079..2e5fa96b5 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -147,7 +147,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorValueBitCount; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 67755c79f..9085ee431 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -104,7 +104,7 @@ 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 = 1; + instance->encoder.repeat = 10; instance->encoder.size_upload = 2400; //wrong!! upload 186*16 = 2976 - actual size about 1728 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -282,7 +282,6 @@ SubGhzProtocolStatus // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - // flipper_format_read_uint32( // flipper_format, "Data", (uint32_t*)&instance->generic.data_2, 1); if(!flipper_format_rewind(flipper_format)) { diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 9b97137f9..1f2731f54 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -77,7 +77,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -322,7 +322,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 1e8f7098c..6449f720a 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -85,7 +85,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 1024; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -206,7 +206,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 5e4ca97e8..941ff5c56 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -78,7 +78,7 @@ 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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -176,7 +176,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 62178981c..9c33b11ec 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -76,7 +76,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -265,7 +265,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 3d820b318..13af0d302 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -98,7 +98,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -298,7 +298,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 902422148..ad343968b 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -92,7 +92,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -574,11 +574,9 @@ SubGhzProtocolStatus subghz_protocol_secplus_v2_remote_controller( &instance->generic, instance->secplus_packet_1); subghz_protocol_secplus_v2_encode(instance); - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); - subghz_protocol_encoder_secplus_v2_get_upload(instance); //update data diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 201fcd9d7..217dbb780 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -101,7 +101,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 = 5; + instance->encoder.repeat = 10; instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -178,7 +178,6 @@ SubGhzProtocolStatus ret = SubGhzProtocolStatusErrorParserTe; break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 04dba4736..c9f6f47bd 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -81,7 +81,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index 4d4b128f1..2be378b7d 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -82,7 +82,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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 512; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; diff --git a/lib/subghz/protocols/treadmill37.c b/lib/subghz/protocols/treadmill37.c index 8a7badb94..e9915c296 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -73,7 +73,7 @@ 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 = 3; + instance->encoder.repeat = 10; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -151,7 +151,6 @@ SubGhzProtocolStatus if(ret != SubGhzProtocolStatusOk) { break; } - // Optional value flipper_format_read_uint32( flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1); diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index ece8dde2c..dc6add277 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -684,8 +684,6 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { 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++; @@ -758,64 +756,48 @@ 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)); - // 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) + // Connect CC1101_GD0 to TIM2 as output furi_hal_gpio_init_ex( &gpio_cc1101_g0, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedLow, GpioAltFn1TIM2); - // 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 + // 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; 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); // Setup DMA with settings structure - // setup interrupt for DMA. When DMA generate interrupt event we call furi_hal_subghz_async_tx_dma_isr + LL_DMA_Init(SUBGHZ_DMA_CH1_DEF, &dma_config); 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); // 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 + LL_DMA_EnableIT_TC(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableIT_HT(SUBGHZ_DMA_CH1_DEF); + LL_DMA_EnableChannel(SUBGHZ_DMA_CH1_DEF); - furi_hal_bus_enable(FuriHalBusTIM2); // Enable TIM2 + furi_hal_bus_enable(FuriHalBusTIM2); // Configure TIM2 - 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_SetCounterMode(TIM2, LL_TIM_COUNTERMODE_UP); 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_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); // ClockSource for TIM2 - LL_TIM_DisableARRPreload( - TIM2); // Change TIM2 setting immediately (dont wait when counter will be overload) + LL_TIM_SetPrescaler(TIM2, 64 - 1); + LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL); + LL_TIM_DisableARRPreload(TIM2); // Configure TIM2 CH2 - LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; //Settings structure - // CH2 working mode - TOGGLE (swith between HI and LOW levels) + LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; 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; // 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 + 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); LL_TIM_OC_DisableFast(TIM2, LL_TIM_CHANNEL_CH2); LL_TIM_DisableMasterSlaveMode(TIM2); @@ -823,8 +805,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); // Setup calling DMA by TIM2 events - LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); //Enable TIM2 CH2 + LL_TIM_EnableDMAReq_UPDATE(TIM2); + LL_TIM_CC_EnableChannel(TIM2, LL_TIM_CHANNEL_CH2); // Start debug if(furi_hal_subghz_start_debug()) { @@ -859,8 +841,8 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* #endif furi_hal_subghz_tx(); - LL_TIM_SetCounter(TIM2, 0); // Reset TIM2 - LL_TIM_EnableCounter(TIM2); // Start TIM2 counting. + LL_TIM_SetCounter(TIM2, 0); + LL_TIM_EnableCounter(TIM2); return true; } From 7e851684959dda8d8c6059d01baf281d5897c961 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:32:10 +0300 Subject: [PATCH 021/202] add comments back --- .../drivers/subghz/cc1101_ext/cc1101_ext.c | 11 ++- targets/f7/furi_hal/furi_hal_subghz.c | 78 ++++++++++++------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c index abca098eb..e24ed8f7a 100644 --- a/applications/drivers/subghz/cc1101_ext/cc1101_ext.c +++ b/applications/drivers/subghz/cc1101_ext/cc1101_ext.c @@ -794,12 +794,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)); @@ -821,7 +826,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); @@ -835,7 +840,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/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index dc6add277..ece8dde2c 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -684,6 +684,8 @@ static void furi_hal_subghz_async_tx_refill(uint32_t* buffer, size_t samples) { 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++; @@ -756,48 +758,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); @@ -805,8 +823,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()) { @@ -841,8 +859,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; } From 914ca6f21d05f89b93069bb3f8b2edcbfc9cdc40 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:36:06 +0300 Subject: [PATCH 022/202] Revert "enable winter anims" This reverts commit c8e756a3c52a615514c74951f9ec375b1f956223. --- .../L1_Happy_holidays_128x64/frame_0.png | Bin 858 -> 0 bytes .../L1_Happy_holidays_128x64/frame_1.png | Bin 855 -> 0 bytes .../L1_Happy_holidays_128x64/frame_10.png | Bin 872 -> 0 bytes .../L1_Happy_holidays_128x64/frame_11.png | Bin 861 -> 0 bytes .../L1_Happy_holidays_128x64/frame_12.png | Bin 853 -> 0 bytes .../L1_Happy_holidays_128x64/frame_2.png | Bin 851 -> 0 bytes .../L1_Happy_holidays_128x64/frame_3.png | Bin 852 -> 0 bytes .../L1_Happy_holidays_128x64/frame_4.png | Bin 856 -> 0 bytes .../L1_Happy_holidays_128x64/frame_5.png | Bin 850 -> 0 bytes .../L1_Happy_holidays_128x64/frame_6.png | Bin 851 -> 0 bytes .../L1_Happy_holidays_128x64/frame_7.png | Bin 860 -> 0 bytes .../L1_Happy_holidays_128x64/frame_8.png | Bin 857 -> 0 bytes .../L1_Happy_holidays_128x64/frame_9.png | Bin 863 -> 0 bytes .../L1_Happy_holidays_128x64/meta.txt | 23 ------------------ .../external/L1_New_year_128x64/frame_0.png | Bin 1740 -> 0 bytes .../external/L1_New_year_128x64/frame_1.png | Bin 1783 -> 0 bytes .../external/L1_New_year_128x64/frame_2.png | Bin 1754 -> 0 bytes .../external/L1_New_year_128x64/frame_3.png | Bin 1745 -> 0 bytes .../external/L1_New_year_128x64/meta.txt | 14 ----------- .../L1_Sleigh_ride_128x64/frame_0.png | Bin 820 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_1.png | Bin 881 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_10.png | Bin 788 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_11.png | Bin 816 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_12.png | Bin 864 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_13.png | Bin 798 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_14.png | Bin 813 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_15.png | Bin 879 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_16.png | Bin 855 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_17.png | Bin 772 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_18.png | Bin 817 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_19.png | Bin 867 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_2.png | Bin 866 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_20.png | Bin 809 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_21.png | Bin 795 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_22.png | Bin 870 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_23.png | Bin 852 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_24.png | Bin 805 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_25.png | Bin 858 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_26.png | Bin 830 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_27.png | Bin 828 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_28.png | Bin 585 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_29.png | Bin 431 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_3.png | Bin 812 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_30.png | Bin 281 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_31.png | Bin 270 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_32.png | Bin 236 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_33.png | Bin 485 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_34.png | Bin 771 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_35.png | Bin 887 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_36.png | Bin 809 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_4.png | Bin 890 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_5.png | Bin 819 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_6.png | Bin 799 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_7.png | Bin 817 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_8.png | Bin 875 -> 0 bytes .../L1_Sleigh_ride_128x64/frame_9.png | Bin 823 -> 0 bytes .../external/L1_Sleigh_ride_128x64/meta.txt | 23 ------------------ assets/dolphin/external/manifest.txt | 21 ---------------- 58 files changed, 81 deletions(-) delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_0.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_1.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_10.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_11.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_12.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_2.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_3.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_4.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_5.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_6.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_7.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_8.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/frame_9.png delete mode 100755 assets/dolphin/external/L1_Happy_holidays_128x64/meta.txt delete mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_0.png delete mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_1.png delete mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_2.png delete mode 100644 assets/dolphin/external/L1_New_year_128x64/frame_3.png delete mode 100644 assets/dolphin/external/L1_New_year_128x64/meta.txt delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_0.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_1.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_10.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_11.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_12.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_13.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_14.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_15.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_16.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_17.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_18.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_19.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_2.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_20.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_21.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_22.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_23.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_24.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_25.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_26.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_27.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_28.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_29.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_3.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_30.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_31.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_32.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_33.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_34.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_35.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_36.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_4.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_5.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_6.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_7.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_8.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/frame_9.png delete mode 100755 assets/dolphin/external/L1_Sleigh_ride_128x64/meta.txt 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 f1207ed14b35cd01eafa00342f2801d74b46c622..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 858 zcmV-g1Eu_lP)b3G}_zPLJOisK}_wXw<>5vl)fnyEcmLJ)0YM`-qsd9LG<*g zg3uHHfWB#8LN=zM6$A}^iEZj_uEAVvNj6Dq_mbTmAFd!cFTa@&W`4{t&64fs z1O6+|kM;zum_y0;LR;%E{4|RT5jh|65*n~`w!6HK#2L%5CukdP2q1}?sRvZR12EsS zP6BKKYE)f>e;Pi(R|nZq8>DGO9+1VXzqs;Y z>zzX{C5FwW0Jpr#S{j^JpU8;?=-uOueU6(!BS9RndoqvP1aOo&p}tH{j*$Y>F2CbY zJTRxpb}`rb=#NmV8-pBq^GnU*vUM}Lrs&3DVX2NWQRPbV9Eu_gHSp)RdG_xa2L$n& zAzo{&Ly7kt@}aaYyloeLFiehuLtK&CfvZv9i~0aRdxU@_`$?=4uDcSHyHB@B=3sjv z1Yp-C*-y?RIqd_Gb4_y*`NdA5GkU`(19o4%V_h1`c<^#Zb!*T(J_a>H!@b?Pw{we% z1dHI&$2X)22_qD0fIX;@)4QwO+?wU}hx4OTV?~Yn&m|Y9uV0s?!lVme6X@3g$i9tJ zAw<+xNi6@`UU{o9>1u#x3Q(N{@}pCiFtmK`&hbpo$P?pJF1bH9`LkVT#6U2cFbE**&wk(4~aXcues_UO>rxTx;jl=<04a40ce_veR#+-lBGlmLLRKtj8 k04biGE0u;J2K9h{0X->d_eOezlK=n!07*qoM6N<$g2W|{hX4Qo 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 9d9012281f0193620bc458455f6a93538fad2e96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 855 zcmV-d1E~CoP);NOWB)|bSkT1aRXRBA7yY|TxF{FSfXu8*lUk&&qCXhO8F)FkoA>H2*0;2g2fv& z(d#Xs;?yt-?E~*0`D{sLX^yAU0@$ekWb2zI3N8X{Fs83orr0sQ#X|guwcpNP1=Z{G zSdat3yA1rXr`2nki#Nt*x!NSyKj4u+UwHlRdp$A`_S7#~2^ znalc2P!kjpqG|x})i68iBGTxNe8ACYahNl>a$BAkJMa8EWISSW9x^|7!7He$DqzaK zHH~UJtLg*3q0pI4GT^O->aCwBq?&T_iw|3e_BWDe59<_EmFTb&R`!3M<{$oTix6M< z>)gGM21gU)@>GCLe`>uG{-^#CUE~ZdT5WwA&`g90koSOKH57+|(uvFv@ zu5jrw5_rGj>oQ@wX!V1Y;}RF$?qIcGbR!}y4R^HsbN*(0SXHAg%c)Z)Sl#Bty=5=L zsgMqRmyS4iNFy-1b&V$Edv0=qawEuV>%O>0>(QCF8Rm}903b;}$!G~1tcE58ti4NG z#Li1EDY$>yq#tK|B{A92+mN-R_2;-oJ?~InHFlt@zLj6-(yP6H#HwvaCccFQEMv5L zf=vPm?`HFc&NPYoHp(3VcND9qw+G8F!`Z&vP|e+|N$-{9)>3?5mJ2g}w){{ph6J4J z%MI1syar5eO=xJuFUo-&*Dz1D2A`uKvLp&GC&Xeh15D$Dcrr0Le)4jNZU3t?tW+aRjFfo7> hF5f7XLU{2m{sTJ-XIlu4y4L^z002ovPDHLkV1fh!ld%8* 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 cb8f173b0bdd8ae661a0e3c000fa88fe256b3a30..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 872 zcmV-u1DE`XP)?*-2wV(L$SR8{9F5vK7gWF*K9z%sdYhFTTrr?}P9A_;@_Xs~B$& zdb=C`2i$J}PvkF<5Lip!C9yJmJk zyD)1^<~b8;r*9}UQQZePJA_5QLN`m13s@3OkFb+2eNEr3q|e{3Do1W(`LOHQ*{Ccc zB5$Q4jUTjXj@h}Yt9&X@PQ4@to~kt#+CNgxBxB&i*V>Ql>6XdOn;M1U`%k`JdYzb# zxvXjd@yA2gSI!^4_f;b4ZVha>V{2(}QKcwlYoN5(9sVe;;QMGaV0UFMDS=xLDQI_k zl`c30L1j1u&AC6(zViz%&P+gL(A6Pd>t%<3qVh2Bofy|h5cY3#mMO5li|Y^PWBb5vv#*6Jr_FPc<~!% zzI?osA0I4#%i;aLLUqKAW@~uXa`(Ts#~MgIadhi(T6M8oL-0000;RtW zYC;GgHWEVi?&N>ACW2t*m9`GWO9_C1mL9w88*tuE7R2n%(<4e*f~ghW9``l2w{(Idzmy zutB!vuh7(q6FumKEzuwW3j>H)yTC$Q)<$cl-5z|y-D(1|M{-+S4~zn(Om^kCIMPHS9NRn-wo zb-G{73WfIdhPR*xoUE3%!LeNDq5+&-b6)u9wbmmCRLA3PDHsgjUH*FU4P5pLwkgFV zmxg|AeEVE-xl4Bs3UJRKUC+WFO%Y=k1n4^CS3kxxCIG~RLNWKb&+NDt@uDzso1(=) ziO{b`1j1wkskuLG`gr?8T!vo%MDt|Wxs$H6RMRzvDm|*B)6Vsz{HJQVCiK<`7I%66 zpD+s`5q~AZZ&Y`vK%F#^#?`Zh(igxfh#;bQczHYO!l%F7Qcd2Iw2#!O6k`4kFx=;dmqLL2aI=r`kQ=1B2Is`ynS&V z<-m2T+}iw1!W-ao64<|YDyCoJj>kKX`oA%;cSfI$NThnHV{pp1@th#<3}a7jmBu`P zDqsa5kKKJg0*?!;XMmeM@@8qw(-6&dL7+AUbPP_-V{~{se>K-hd3t2Z!}?=`PrJ;# zQnh39NRQzpt(~lq3*y4nYpOBlET<#Dr92W2XR56aRW)_yVsXJ!W4uiuWfyz#sBd~p naeM(t04ZO&R;l#2#RBjz<}zf`VzpON00000NkvXXu0mjf*Yuj_ 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 5d4c7e7c55fb7012fa223c9084bfe754c5b50e4f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 853 zcmV-b1FHOqP)24#t z)$%{!Q45Ty-Up3=wft=c7FG4Oco@|*;BzJ3rR*z7I#_C0{0L^xB_+HdKGwmDt*hX6 zDH20Ibn!pi0Fyv8^LjZu2{ML(&ZIO+ZL(v#u~+Zi{QU%ZB`c)=A=;6(xnT!)w=IGt z=rYN(4d4fF8i~Na_14g#ID(kx5x^!L#~NS7XoQad>-6bk!-Etiv{*>*vHHuoDNw!k z6Bgu&=ryXNZat%G8DUk&U%K4Vh#e5;u5{tcO?gnuU;Vpo0uJJxMn=zn8dOzP6(9`7 zEDrjmYFK79AIVDKNJDk`d#Z&N9J_G3ad7`tJ->0As;ZJa<+8$t^j++69Ulc2wFZz}~l6 z2i~c<%n15&MiH}mc>V#t(I9v-Q%ybCJHJ&K+pAM;f5?b#dvfM$7{k&>F~d5MxP|iG zuzq5)*a~gZ@(lPVvHC{youmzh+FR<={yxpxF6ZCPB+GWTdbU|UpW2_RHsYTJ;tjyJJ$FNDVQx?~LS0I5z~ f@9uVx0t);KGZb(kKhPw)00000NkvXXu0mjfXi1u-?7W>nA=QF^N=Sn#Tt)k_1K?$nA#5M6H- z3SIFJs3+S?$SlbLxQwjlT}@4XMa_vL+g&bDiVrlO%h|I11tkAJR*h4?o`ocsAzjOk`v+05A z6S0AbLLD1$CbL%`nE^E%K>+(g<*^MDqF^JyR=fZHhu$#kT7e)w$r|myavG?9auyq; zKYZndksi}ZIGTPdw6*@?p1uO!w znz&u}0w4q@tEFA=Y*Y2x6`WjihCh3K<3L|C-CkX!q^iUR3udD4T<)wc*+BBe^$%O` z9DO;pT($(b6I9nTb45d9FA<=3UoiGLeh!Tk@xblL1OCRq3Fd|R3OxmeO3e8Dibr{1 zUeld&q4m*kk=8c%bL`D8HBUyJ+v!b3H;0AgI@Uyu>*@0-ig2hwxUj=>f6jU!NYIQ3 zT4Nn5ydO}Eq;>UexAYxw0t`u2rUq`rdtV~p$Dq)4{V=Ihabs3ZrTcVC0t^1*bx8)pZ36wySQ=&?|`G43`_qCmSb|uFJ9h)-AGPdfGG$gIuCG%s+c7RI1Xef zDMOT|0C&IEz1}^e&Fwi(K3p7`9xLnjeV5W}Gq-NZa%s{B7{ln-2ISGra}oGVt&uw6 z&4cn*Y0_7N<_b`o1d1cmKVj+UrGHN4dS0F!pY|#IzQs2U8)eLn$s>IRC)sFNgSj9c zT%A{qnQ&zVJj&x{G*jL9R6CvI^js_+_-a}HHpK^%`VRKOQ-O7;6vtCo$t)n{i}RJr dAS9px@GqP{Wg0BXhk5`2002ovPDHLkV1gwEk39eY 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 3a47dfc170224127ad04528accd7b47104c33ae9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 852 zcmV-a1FQUrP)`u%7fF~_5srm=#DzMRcpHmH0U6ukyH3_+-tnN~JbCOQv8kQi2h4Vg#S0unXINGxg zZkHmf(E0H4f3_jULp=X_eme(@K&?>6D75yyarmQomH8?DIxB$n{l}YMtfCMgz$WARRBeK`i7Xb9N38#N{wk>6 zn8t$ai(jKW?KTqz)31beoqg$Au^EG>5F@0_K|5OoAV!jm2e98^DtSPu+D-&p6T7j? z22=T#GHT)qf}e{4E{@RF0Fg$IkAQ;u*%3|?$ZdIC>Ad>eun9>hhRF2Pg|Muus(`6R z)->i?xoVVmxigvOz>5vlTi;O5G~@UuZ#56>ZKQucT&Aq5_(KI(+xu}=dbnaMkX%?k zbML*uvD5@?3a}MTY;@e$8so!6fS&!)_@_xFgSiv|g03v0gM*XQC7OLe7Yx9Bg*ybo z<=>Gw{DDXpaM`TYH+n_1%^eCrrGy!?Ak{p{GzI z8~!pIEf6A&*y#3kR-v@#Cda7$LP?wVC3xSi3@2 z!A?tGJB8q9N&6|Bbg2;*OYx3%VpovwG3?>ipJCRCq5JN3X{KARhKwUBo|D-PFjKokHKSty^2^5?rk6=KdDEDqO$ zlE(C=O?Adb3-7cQG}Eo^pfh<6C=Srv}f|5b-c_7Lq9ky$8NSLWtCuN(*43ja_;nGG^5I8Gz^?q6nP)ZGpk{ZHis=6xRwwiRTHA8TKI}=fhUKOwbyp*p) z2p|>-q51KR|71-Kg38;iIutJ<0D2>80Fl&>=|L8@mlf0m2&d@4?5vamXlM>`E$uM& zLKMOdA7KG!y!qBs72u{$B7pt6@44MDK-D!^V87mRI{WLlLppT;;t_9C|0|_TN*h>^ zj^N5re|xwI8NVphb@q+h3wV$~8SxX!(74qyUqIrNE$9sE#={~=qPFP+W1vSt9(&E1 z0@w#M;|jvVascq_~JyI!tV!l;9SsuG>egr&l+Zp2-t5D{M= zxWD<`Yin~kj-C+Uz#rRbhI1ica{}Z${oyYtVl@D9A=jSz95#dEluuTe-J(51Z@~=S z7ayPQ^NX4scV{*~`TbBe`9Qia-9u8(wg>LXe{9TRN|XGS=5{@Zg<_dt<(Sv&+FJ*o ziN0^Nat=6q6b)+J;3_!3{A4WGN_o||LH+#NqgtL0dO9am(d!IKB4BSt_PHey6Isf7y-< z!wfa!9l9>;)g)X8pEG;~IUur8_8G`M#z14}0c@3I_-9^=i5){Fe2nIfdcshi9*ImQ~PmS^Z0g~t#yK>Pt#X=O#0ChmR iZ!Pxq^*{`ifqwzxpI*j|*hYB(00007mXC8pbK{fp*z8BBm>suB`z94 zFfN2z#O6;ZBKV8g7q#_QCCN@qXeOGrX(7q8#^xpS-n%X)ZoHd&?}2j;hXd^?#CwPA zZOi|FM=da>x(?a|HqzI5V_8*ymmEel_W3|*?omo@k`A{uEM5S!qfr}P5)T`~@!}eU zdlYFxI&$ehTOSiaaP7tRABvyQ0Q#b~$wP^DsbasocXJFd1{r#UZe(Mj5kjgVg2g{! zqNg^2ZK_BB-fyps9Bu~itIHvPje7exFE?RqvK{*YgMaUzt9`2BE?9`~vG&X8RZ#K) z7UV#1mhwdS08C|FSktM~pXWCN$lx48!Zft9CIB81XRJb3h^a7vAc@-4!PamLsmo(G z7;ooH(D5G)LNpx!ygxvW^j~~s^Wd{{%hC2_%Bo6qC>J(LcU4;-n|et6 z;lSJ~e$e#y#ZU2?PPWj?9s!L5~f{i={^$pKjX?pAQF9f0f{TU#lQ!M$*KdX4t-O&vGdPrS(ARtsZ%Cc+2!QQ{=VDb z$ZDTt^&CF36BRUR(S*Xj8=PdQQ7miHV(rM{c2K0j(_N#gNlTJmk~39ouyWD33IS_J z7XP|CXfrx*)k97CSv|YB^_+)F=h8LRI45=q`3b{5Jz4>f1FM)q(809%5-`|T2Vru@ zGKlX3frLxo*sC6#jq*=|cLuA^-vNb@?tE>=D{0cXn5jm8+S&5?R=a$R3xT(~^R*eT zpaBK_K|^ER?D>|0W*YN^<82^co0-IDeXEol(ku3ud+BR(w;t!C()#IT&PhZT>YIlj zV>2mDWvKY@{dLud2K0bP1Opc!9uJ{(@LR+Or^Zxv0OniRg=d^G^)Q+QXo8flT(4F` chyfh@3(5jmrgn0s#I%4<)Bp&osQ-~KgQQ%<@;Hi6Q z(k904#d!5(@qoOhMQKuPI7tL$L1LF^*jJ?G0dL=USP~QG@}2o6Gv9n?23nDej}kdT z%m0YSEikG2H%!)m?dFG!uBqxWM%Pu7wAs|rc~xB&@LEke)|w&YfSVIhi(VG60lZkK zLkJ)i38DG%)&FEo4TAZ%TXiUYLICte)Bqx>AJBu`+PbTtCO|kw2WESz9KfOG5zo>N zW6wt+Z1WKo$c;DOdU_tXrjrO@zvz3m5eBGw1`F)eJ08pa^3{+|?SlBkTh#wbsglwL z7NjG1bEv;P+<=TL33aW!apM*~Bv3_MLKP0Lx6BujIAs$$!@BXY2$HC6I$#WRKPX@? z=B5C40L^%U@TeL9Tz-aQyl;>-6FDHG4=Ro}JPG;MZ=V12InxGY0+11nx>Z$m^oHv9 zJ!00#x3=d1ACr6Z*?8-)XLd*YpYD=NHRbi`cN$$Ut<8r~2SrsSI*|#>#fGlOU8Wcj zUm3W!@y)9%vw8NP5@6SjZ8yW&5U_ax3Y~8Fb4pkZKs?B|r;dlspfu&k3YAUTGxQeC z;5~6Tx=$}?a?qXG_~f^Ts>ucEK6eL6ecK**NB^;2!IUTYHO)7Ch=o#>VCjI@>e^ce z$3)+kI)4&4u^$a;e8_u{`}qgpV!5CiA5uTPvR5nM=;@qLjSnR`MUv~wN8Fz_&NpST ztE3)r-lspDs^Lj;ii`tWV>=jR&xu_{?u0VL{y86*m5xnJE=-M|y$MLj$k!`-mlsi< zTxzKPaa_VxaGVFo0};g;Ab(sd-V^((KVFjIE7|@zXYq53SpZotj{5+^U>rC(`QTCw zd}g-JFvE8P_T8lqRy2G?Q;j+67zeWbbBh=q+%0OVSLKD#IiJk$J6tQSJ7w5Lq&0n+ zT-->gq=I;GdqFj#3?=X+f`LyVo(`eVbsO=(S7ZEtfFwG=E}eCzRE(k-pbkj)^@YB^ d9*BV|@Gpq#TX#gkE++s0002ovPDHLkV1lDnjsE}u 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 da3a78f4c7d2364f251efcf0d311cfeadf3693ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860 zcmV-i1Ec(jP)HGw^KWKzD1O2?a4gD9ViVO(ALi=Ao3~!3nM+a`evB?;V{tr$^<9Ht z@Ozx-`5N$}TShE+x8EB(Sg}yy2m+X>`*`iE3Tp8XV3Q$zVtka;2@D3}hg5HzodeaM zuV6r)vTsm07j7o>+()SD%qy4MY8GPL03l%(8fhil9EnrbU|)z+i~uB2(+ps1v~_mb zU^43xhMmAih-NLo^Z-r$6p@vVC;(iIW>)CMlbf<#Y`*@W-|d2p} z`R|OgGP|3p27rY^b0W!r7i;O6Uw)vFYRd8R@6`71T~K%Z4h2;uI^c%mdrNwLec366 z#9s{jc5kkyehI4U0&E4N8_n>0h2Q;HfR25^&}VTGcrHNzuRRTD68cmrOE9xWI~;@g zRa*h!!Xw1`zY<8^2~%tI?Zu-K7rhR99V&Q&Uq>%2}c?`l}k+I{g3Ek&naV}M(`v%`QS-6Vs37`vu}7BFU+ zw1_z;d5(bhha}y&C&I*$mHU1m6Kn? z6o#RHp1Ib6S_zl4d*i*e8be^Bwr9Z`#3bzima(q3{<4?X#IMh*M(>-2!l_2~{L;4& z!N*-~{betw7I$4WbWE^ts-d8%#zHc2259RqPvK~3t8?OkUX;Tl>$Z m(G*Yzq;TDI@s{A_#w=kO&avP_}B;zHLpOl zBas;Lp^N{;2ABlG>9-nlBtgb7(4I6xQk!hsPVA5OZ~bv>g;d?_qHs8 zCFn58i#6aoZwd*)zy03OfjESiml41wZAWWg#%PF-0BiK>lf#1)##t<+_gVSn>=jVG z{sR`|nea`@qtRMM#}dMdj=y@fsTM+t=MXX`pxCl2s58CN>+<(53EMTR1?@1s!UhNz zA0T1yGfqsHXe9d1ERt5*nnQEm^aDj_SFZivmu{xz3DwlI96M&h#chtg~lh@tevO^(pN5SMgwUHpS{$p^2~#jlQbK(aQno=$AI7%fJCweu7e?5MO* zfPHAQHoOzFnGy80j3Q>`;M^m8qe1X&rkZ-Ndv3Edwp*{X{2?Q{<;lBW!5EfaifPse zC7K5Zz}oT2V$<2A=>_n6u=;lW{iFqkTAQj<{vOR*uI87flO;P-K3Q)(pW^Z8`A<00 z+EktLOB!;xepw@9-OS0lf);uk82K1zs!okzwEJFuxzGuDX<*9X{k6sy6*H-vkc1-A z4-Y&=ZNpqEPzm7r4b_BUlYaz4c_8Y}RcoQDmkiA$5`a@kJquYuQ+46qht*5 j04Y!0=|GNY91Bxel{hfU)#i;bjeXFf0r)cg|m z+s-(^7BF=7szJD24*=ER!!^!mfoCz4qOTO&dXl>^D)ywH&zYgxW*M#RUNTZ zr~AaLQfyyu=r(kNlhx8TIFjpJ)Q^*E&T}8W*1G?Y>Udn1qQUU(#jh9Nz-7N^o35DT z!r-rsZ=OjmcIw`J0^IUPH?!~uQ^MFq0Xh%+!yn-p69D2uv6TD#!|b>h@q#e1M#-YT zi_jmA2!zQ7QgeUW_VEscxD3AjvF6E;b0b}CsiqqYR=ZV4r=9tv@`q}=A@tM<7Wa7e z?=TA>5q~wpZw&8Jg*s^>jmvKpyS^|s#~^};=EKW7Q72{kDlOIIJxO~>ohqL9R~WaS zZiU>X=tpQTfTX=xKIz$j{av76>=oq4FoWFzCqO2oyoPZ?PlCeq8MbyKj(@ehbAG|7 zcWBPx*hDk(0RCt{2UE7WvI|y{I=Kp`W^N?6#XebJ7cRNX!w3;1X0AsGs>YSsscD6-0 z&u`MlajasswG2DsXC-l|Y^3<}JTDi;qPlMf>>&Hm7DfOD84o?rvozm%yxxDsUu6I< z=3{9JV8Et{7i;i%1bFc*9*+Pop2ceb1a_#_SK&2)0R8fJHiD5h`ZWLoI~S+&;wwh5 z)Br}k=1H%UZwK;y!IP2(*}LB`}3d6kQ}gYuuu1MGTK##)IXVJ=)&odxjVpiKSDoE|IiC z13-+cbI8jxU_FPC6B$8`V&!J5THmdO zTV1Cxki0+4B8yp+huQHymsJc~v+UZA&5%oacGa*xmUns3Au7uZ37Nfk6eT>mdd*Yc ztF5ty)TfuiylC0sMu44>H6YzRJpFxk$E6L*qi^ZxV{K&Gh^qHn0!OgqLiBdc{~EL8 zi!K}SDMbLU=$d(;h;Am7MisuMG5dSx%Xn!ORsn3`B5O;&1hT?Z5bl4sF57rhxo z5ri2-QHtR^0N!>h4dgA>J|shAq-=i90KKI^18(j5uYpYHK!SK-()-=on|V0$*6ohj z-e0K!s3I`yp%!%2j`l_!LzH56>G#ohdEQZYI?{7g1kmAv5z>ngtc6>76wT{3s0iEj z`zr5`XkQHFJkJwVkJ05K1F0H7h5jxV8-1VY{T{7M7f>>QtcU7#*&H6#Age0r-BHoW z#Q>V|4j>6Im&=Ce%9C*LmK7ReYJwsZy}RzX<9VHOqqgPyfCWl3%7nSy4}P9d_FC)8gmN z%X6S2ljYM|5CODMtc7VI!(P-lOf^De!XQ&6dvr(FW#JX$qY(f;1kEVYG8Y=KrclNHFc9WjG^|K@aotdb&;l1 zsVy}B%S#}-ApOF`GL80R4x`s0&*_DmRlv!8mWF)Hia zC_N*3>Hj`}o@eq@wFY23pP{o8GDa9VO|*v5!|+ouqx4Y&nFXdWx=W6zqlwaLBr9uZ zLk7SK5*^4hA~y6hbDpd-i>^B{>iHdM1IQLFOd&E_0A}Rzim1`=EPvMfqk>tVN$Upb zQ_h73a7T!ok-IZAbYbclSrn0&x?}~Av3L)Ao_;>;%h?QpJRHGVMEE?IOcJ_A*S}u8B>r*;hMc>iSm;9~0|J%6# z8DSgv;@`$s1n{0jt#Q=aWauDTRXP>Ea{g&OpmF8}G(-P5|BP0ZrUk1WDyw{5$W@gP iy>#AXgm)UP9RCFdzW|y^yUYy$0000Px*u}MThRCt{2UD0mjAPgkc{{JuM9!^xOV~@wiu!*`ORc#0ugN;3=+q4f}a)PXS-t3)>^W6Y*4%$>8e%4!ZFzCwRJZeAEG| z%fVGZT;=IJW~l=p<1!aKpLC9~^gPe=*U(5;`6Xpp&z}TU24?GUxp@(IL@glDF-sJ#Q7dL(4(MY|0d}qxyV^r;V zY||W3%8RVMuaOxioi9p^HLjuY5xM6bt^;Vs(I)raN#l;*m~||6qoR4*lZ)Pu#@C`6 z8t)mCg>+iv#3~2Sl-Cs9nTKU+(89LHUG7AKDV((OPRYN=3 z(f!{-0=w)ECND;a3>t&VI4@#&WnknPrNk_}>Z-p(d!PDsaMV#ECzas@o}o3_qg?Ab z+CTcN<@gFn16$Ei+oP8ZAB=Hly;s|C>z$<+8uOI=U!|Y67i?O%{5lvpfR!#7r$Hf5 zdpASq?(6_j!d8vT;cCuJpaiqIGYexvIf8a65_KlFD)RQtDg#$=E^pn|d>0ZZ7pYC+ zDWCCfW+O(qGbJdK>a>kx@~V{UrL)Q?pgEr>|H1*rK{MW|u}>@fMds;a8Dq?Z+0jUv z980cdU~~#s@Bk?nI&f>(kA%&p0IR?Ca};;Z@ex#W04AzTmx0+F&6Yr3XtqkKXg$vM zuX-Nmtaf0la+2?;2p~6jMoHGgvr)jyzo>N7=3ZrZZN62f-;;fDl=D1K=`ARqNU*0K zfhjvZ`HW$#b*+p5h%3y(I;4>*VA=sZVEmyjvNse6D?fK_g>^*B;AQVDqB z_34E0Q&6jb*5a2gY`YbjwEoQ89hMIAz0_gFcR6oGq+;$an0eF)fCRGnJsJ@h2lL=X zsfd16ST(OldfEMy{a_hbRSjt4D>eqnh^7>;eFm2EGm%nzk93xIEuI`00jx>n!mQlP zmOK_?UquXA_K)ONMMf6eBLK4yc0v|bUX6}t^^y5GQjTVIL!~Ry!RyCz4Yv#&g;nQj zONp#Zk7QP@XLY(jgtb3grSq%~FD*x~;htH*aa=#4f;`n@>#`q`!S=Vn?2GK{=@dc5 z!SgB<^)rET<1)Esb^+N_i%v~8ooM@F>5Ozj;~2e}Fz=-j8C{nB6EPKJj&TAM8L9|a zHrGeSMWdVM2yB!cp{4+BQdmlWHZ2@8V7(qWMYNofay-^+)rH)bM$MF_nz zhnWZ6CuG~0bw%^Ov`LqZqLT(W=kdQkAND*$X_INn%YI&JVHeM!GW1(IjcbbnIhHjC zQ>vROK!!*w#AS(~w+WtOp)xOP{PE|iwk%kuxtv~NUp4t7QXdM9mL1-h2)P`MZaF79 ziP17P(-uX9xJ9BKb6`~m@WN{({4y*#5&=*h$vXVhEQ92#L*eHDy$STqMcn0E0GZ2|8AU4Qkn=@r9i%K^rIZ0Kj- z{S`#^Aj|=@u*}q*EC-lL1I)rLqB#4^&d;8HtE97{1Y54lckaIt#4OTtge(V$fRQ1c zdemEUc#mWv2RRR0OBk&dh zyxTw2&fLt+$bW)9Px*lu1NERCt{2UG0wJFbGUmeg9X^{Wy6=$p8b`Nw&KqRc##`Fy_bTg^v%Bbd@gC2h|;jIfvUw)6~B84;Imz}5Oie&R> zA&e*@WxDI|r9ATaWksM=fGqgNXZdUhi`FB$7Qbj7;$<00HH|S*Pya4Z{cS+&6umU9 z&;ZyUG@@tPv!yjwuSL-TW{kw5QK>&Xph$ygq0iDmviz*~x49dr0i*!goF3)qt|yJp zC~WI_ReoK};JvDN-nMtS%r3w)1}2AFLd~i(v=Lai^8%;KJ6aDtI;q>R2wZV^9aycU zC7ajA-SvHj9%W9CIwG&g-H+oqSOY)=G6GlWRN71V>?sG2kd%8xgny z)*;HzZ2j^i%yOoPc6bq_iAR?RQBhWf7uGXj^QRcVswm*mJ*9vxc@t=XDOgZgvIMhu zMDtMSkGcaUf-qwdof&$zU^!)!icUqK-Xsf@Dp(6=`5_~!gnp(FLpnkGiHgya#~B7W z8}c5(!>I<4i?W7bZ8o!p;UOzvx57=NCa=a|zH6RdRABRS23|z<()6hYKtj$G>?A;e z&sFW{tPY5!-$kVcNc|QdJ&&EAox||Z)|~ZL_Pgb&Gw2T^)-TDAeQEjRdi`0$>;j^E z9_2o?#sbTO;rFC|KT0bz!SXp7%5j`8f|xdst;W$B>sg1#-3QBjXPn4;u3^<`nJ$1f z0L0MC#a?md&9Qi}ynYj+2+6F0_W+UrQ~0p$m}i+Rub+-pdo6cv8|VTCK}4)mfivFB ztY_y+SP^B#yXHG1LV0@2(1#Jgn!KtV>=;3X-{5%#R`Io>bhdWVdUyXS7(HXiCAj^2n`zG!bl5au;R$W!L1*X8&G?}&+@tvpX&)-ke5x~MoL;&b|g5buD-GwX)RV(LBDw8z|tp2t@n zihNn?kc`-PBt@j8_Ez%wH_*H>gYU^AgXOn}pL=1|3UyXnrq_8b>#VVr$V3|^YQLhxv^LEO!h~lg zMKI&6N323;kC{|p)k}zGB-hJg&K@iyc(n##JfETS1Tcq|DIl4{W%SU#3TBkvHIP|& zieYp+8s_lXXm`2^%p3+QNVW!$-~|l=smGcte>4yfR~5Sa|W~S0X#4?(A|a{mt*>K^nG=Ksrg#QVqM}{`guiy&j_hk z)Bsr;Y}AQxSIVh44VoLKfap?)U0cRTVg3&>=R*&hI#}CmM@`ryFtaBnNqE_7lWRRtCWhc?uTV<_hoZ2X; zt6&uR^u74q_W;@U)e7O&F2O_Bu=V$-141cV&lx&+O94^7vBo?wAv2@Qh(@-ko&6m_ z%O&@V-sJ&S^lhy}Px*i%CR5RCt{2T+x#3DhM=N^Z&o>eR9en4K#>J=AP-Qt(_PJRD{MkdydvxZ(oew zKg~am<0ebK6MuIq@4cTk@L$1G=6;C-poz|H)$)`FEexUHCZw0dz{U z4)K)we_$&K?7c6AxYhA1n00HPWq%3T?}@jT<~_jb1{}wcmf?+K7JLQBc4QNCiS>ky z)^N1ex-`^U{LDLcA?C%jbZGB)EV#63))5Ki^V(M1C}Hh`Y(IMMU2c4}1I+JR<2Fx* z-M7J{6ER_$k)O@i8QUws?7{s0G7>lx4+opswS3naUO-Lm3~ikCeS{42NM2;~&^!=O zg`tr|#u4S3q8};Cb%5*_HP*(=n>{|uehk>t8O_sLfA)I`xzLs~YhMhZ1A@j{TuA%< z(YFB(fQ&~*v+pXZfgy8FQ}0O_Y2(QR3?ajp=Kjo~m}RR~fMr1RUB#xH$O}oro>X&3 zu*yAHyqFr-!qq2DBdMD0RwTOXnY~NM5?-xxL49Erz#?Ms$|XpqyhLe3lXvy=Y+n^L z`5qyCNdk6t*}_2E#Q8JJU!H`jXloHcPtWNp*gB~wT|`*4hv+HN@GTCoD+*Y0Pbr{@ zDqtm4&p>ik^)ODLRSXspWg-aE=E?E;GxHkIHd~vwrtd@ok&+BpmLAdLI`i#pkmY2`iba>asmTF^W+M?@QJox+MVg=}FR z_{0H_krl>i$msFxtBhnj@dOS~`p?2GrXCsNZJ2k;tiFuSozFX}EHr<#HdF+#*nP$@ z231kuNgJ&_-;I-_^lpD0%!FHukRjz>-Rj9kI_}caPpXj?v!CiVi(#x)=>}QsNvKm;a4zx057d-hEpI~@K zh4VA1lF@}qJw{J!69>pH80z;H&eoy|V_qen7Fd?7wMQJKs-)f)0jgNKr7+@TLvPdx zA~_P}?1-+&oY3c}KGq(l-^a5o17~4Z#-@02^*`F5* zQ5LI?`BUsUhwP`%h?a0Y3pzvDFz~$I#p2SorFBBH#!l6D4vJ*h3*DCX@(}C<+ zC9u(c)|z=~DWX4O+XOeCbg z4rcO{RSvSoYH|@F^vWDYk5*x3)#(}I`mf@3RVVNS$dn!481KDw zLakk%2&qGkM=~qgEJSiYdnl|vo^wG5@N#~nc4fjdx-?-HJ98p-g58uVJuG3g4p!>_ z2gsIXmM)abwS4bYDp%)X7EuRe2zNG0jPWN2V9D70Ig-OQW3Je=yB&kAWW3OwsmL^( z93ZkttRYzrpt0=L-1vV$W(^Q$3X?#Mka@;LIDn?lYiI1yh~nxqt3MJ2tnqSA42P2M zj^pqhpyFa9$^kN9WN48@bLt4`+)Xg0Ud>swOFZo$zC6P#S*wN$4iJ@*i8=^(rJT&6 zL49A5%|RiM9lvr9z@WMUkn*&X#UPqC-JB6 z_OR;MOdE1CYwgE+h5onj+3O>omc0jvAnN&7RuU`D$)qaz-f~X4#?S$zr=!qUwb)We z77-a%2+vjt7AhkEQw2m0_{XMT-Wzn-^di{1jBS^dE`J&(Rfs3xo7-Ri> z*x#D|$D!;5GyPX_wZ12!VhxUdWdZxc%M8yPuSy|PSQv0oD1o%TPW{T%_G nU`(W4bAr3%RJ3cKck2BQ_h}N>mQy;y00000NkvXXu0mjf^7KZs diff --git a/assets/dolphin/external/L1_New_year_128x64/meta.txt b/assets/dolphin/external/L1_New_year_128x64/meta.txt deleted file mode 100644 index 06c2ee6a9..000000000 --- a/assets/dolphin/external/L1_New_year_128x64/meta.txt +++ /dev/null @@ -1,14 +0,0 @@ -Filetype: Flipper Animation -Version: 1 - -Width: 128 -Height: 64 -Passive frames: 4 -Active frames: 0 -Frames order: 0 1 2 3 -Active cycles: 0 -Frame rate: 2 -Duration: 3600 -Active cooldown: 0 - -Bubble slots: 0 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 0e86e6641ad50bc72708a7b895055522815e36d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmV-41Izr0P)1v7=iJOBZEdWHjA-aY7b&xlmQc{Dndu)E{2@XnsnFVNDlS}PQBY`^8LX}% znuTQJqM~4B#zm-92_+Bf zi2p4g0OtWXwe8q|fXG%h&?xNYu0n&b4sL z^QO4X%7Pd6w}FAu%DJt*<9W?LZ}sJzDy)43;DZ5hUZWUqm44ih4)g{eVM}X9JCviI{E9d-bNa6iy*vm1^Sk%H{e(E@vBc# z-aP=|TlA@2q#79;JlIL1;H0w(ER>@j&yA3SNAp3&T#H2>$DO}UvOsY1@;7Fh)u(4; zt7iZxPyTM3-}yp`+0emQD`kSyQQMw0=@Wb0>0bbdoVK6z&aSpu)4j*~fWh82J(ahs zk^ZhEfYWDrc$Q;hgYH!iU><9-BSbx#yM_UN8nQ>e1vk4G9Zmr(R&7rJ9b9?yXnzv? z9;|Z+f{l5Tx$42x*?ZPK)0H*h3u1W~-EK=+bL4|;=wAanQcUf8|2Udo2oy+Cdv8L! zW>BpbXhV_;PA=Cmfr9i4`d*SM(!HKE(xp(jyCf-y=RThl2`wDk>F<@KOZp)req5=I zt7oqy&E?_}H9tt7zmbrnS|wGI<6b=68QGTva1ZnsG5TzOwD^4e+eWX)-a-qNT3C{T yjOM;xOQ2ei-mUtO+R?u!EKPKl0XTtM6#oJKCt7uy9%sA&0000O9aKtjFBFS<5rrbC%}m9M zUgRQo7XRX1b*D;^N?Ky=V?0!jd8EhM#x;n(iGKn8%i01^Q66Wj%mffr}U0-1Ob zAb^oTl>oMustNt26ldfm(WIn8cS^nw*cymV$9HzJdP{oZX z0DX}26@XXvB7mt|#QDeoiB>l%gnMp9<1RSsACR#L4*RrA4QTF}P@O3{P!=X&k|;Kw z{GG>YUjb)6wR^FoRzzG(K&R@4c1B+{Ck6u80E)uu#8e?{x_(D|nn3F|y%Owv`RPM< z0svYxdV1{gIr|r%6wR|rG z_Ipb9%K2T_WLJOdjU!Ia9LO0bFr9z*qjY7Kfbu;WBwM0Eytoa zv${bA+da&=CaBL3UV&D9ecr}20W+2Rq}Z_9H}LJJs7bb1)9v4MC!20Be7t`SouEjX zTsClwx2-l*f?XqA@%P8snc^`e*gjXTY;jv*Dp1Iz~P7fSP6>B%@=;M*NoN z&=$~kJR8Xvu~Q%XbJo)dAR^srD5aiZ-)UR74#8G8#=Rxu>vO8x(`%goMkdxKM;_5P zZ5{Tx07J%srCf@u9_7-oIH4>{e&LkJ13AL?UJoUdiY%}6iF|KsmFfb)$i%E)32)oT z?B9E2TqG~)Q3Yqo~o zxmYX{aZx*b>OB*Z?-*%R3J$iGAKipVIPm_Zv=l_ja&*?#pgOGkGbAD6?ryXZk;1r$ zgpSDY)KMFee7R0UWLTsi-{lZl4&zc)SuWa$RAgPDvy}TEA-!}aN+5?L00000NkvXX Hu0mjfdXb+( 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 5459ae27c3e0b2d87cd3a2b68996ceda57cd6f41..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 788 zcmV+v1MB>WP)EtRqp8xfd%lOk=O*D>zE{8R{hs%EA5QbX z69Qmufyo`u1_Y$GvzL2?9p9?;NLR-=tK3H!AMk?h0wr)JIMuBH8gL~tfxn}}F~9Hl z<$3d+?Z7x3h(1Z$TV(8#I-a(CS;x3zBfP(Wy?+u2f9ZjcsCemK z<&Wal6rHP)KJnQ2Yvtay%j@gj8B=M*sn}#EX+NR&Um>=qX!_pf8Yg>hEzIEY$uM!~ zuUHdl*=yJLoQZl3Qs7L?KGb=y3kHxh*8cX{mbzm{kTmB!c5`-UTn&J< zi;4=Jp|JJtQ2@uGxF8J={k*IYuw2(H9Nf*1CmUz4QUwRhM%rSr=%v%@1-VHpG zX>Y!S*k6sK>@W1)wyDj22^axEz0^)k#2Eth(ON0dDZdreV{iwM$xVfJ3=R;nmUjlM zkIP-KkC1@=DueNqUDFnguRnB%j=2~?_}!KM4^rcK@M}knfZv+b-#PpJX`*(@2BJ)} zaNeHDRUsAFK-0he>Q9Cj2YSXfuX=A1S-C3gx@!a32OlmiuH_RBGzvD5ICz5x0uoat z{>dS!r6pY}w)Cs(4!d3ZqOY|2)f=COOUEq0l(VRO$UN>m{f%P*rjW;&`K2T7Tmw8Z6lW$uEFf_3*{&fP*BdvUl3oztSDYF#=e*{#<6Y z@2PT-f}$j~78AujRW#=~%x_4F-kx6gRJYs_*EOv~0=S@wNBws2I@rmXS`A)kr86X- z{0xjPjE+M4?q1+r4UAUOdqB_spqcWYb+E}M%Vo@AOo3b#kW`T*X<5;T>;4B1IZ1|x S#su9y zXzB&EP@D+}ih{(@X_M%bTA~QHMVd@Y+lgp0)1+x;&YU^F4|6efz3jcey;y7gKP>j} zzX$+u{sV*8eH#$awvz2^UTou3wn^%BjQ2M!)KLP8Whk`@B*5FwGi6BBK^~5Vrc%Bx z!!zfWgB5Ly#h+_Mtr*Q?Q|*O~S36pyZ&YD)F=)-@3=BUtO)xW*`MN8{(m6=&19!`NGz28WWYy2D z+1q#;8#7%4>R+{grs+~ag<|;qeU-2dT)UpNLrnkp%Qp?vkksdoM7O&2zCB2q^rAsE zNbCd5P9!bNM#nGd%QDb@1W7?6sy8)N0=&nOq>e}!Nh$?S`fDTL)RSjdZ#ZYc!?#qd zxhr<#Lh17CddFkQqNHjmoZb7Atjo}DRGw6jONIUf+tP50#f}GqIZXdUSuX>36-<}@nO@B3Kfof;!_8)rw+(TC;ykfz3 z+Zs!+ik#i9-MjZss&8!74os3!|1P@JqO^C)hRgXd3V3~VaW*aQ(dK( zBVB#P?3!PZr114b5K-1ir#FsS0MmVwb-Aa;(!pa$k_xATeam5Q`irBg?^UPoj(3>X zyvk%>=v;R5m{&()cOXqk3LlW9 u3nR;Mq(w=3Ly{^F&W|AlYDO5H8~Yc`rGxxvN7noR0000kltwRJ zHqlzsn8JD1t4U*p9c#2VF|pAm3NBb#Y=vFe5@y-ic^+mVzRUaPd*A#M}wUV0b+eehAL4h6(YK+)S~90XEeon$0@jRSee z1**;&2U2LCpW?xeSzJ_+`+MV|XB|L)n`aCBzmD9N9DuHZ(CO(LZ-gH-r2tr3l^L;t zh=jz-8J6!X#34GaY(O_2UWzjVn5E+Yni{+@Dhu{LyZNn9T?(s@Y97h_%fF3;*|l64 z4QBKGnSYn#VJt@ClE2XvRIamHipXqg_Ee4_ZP<)L#To26V6Da&ME`Geo*HOF!ECLr_vT<^KPyZ&y? zf)c1S`fIsY%e9pgb_i6X!JW^z8JU}?A3#?OD*E0FdvVCq0_AEJVIuEarTvP0NgR%&>G&UbFoz$}i=uVA#~b z@Jo4rqi^uVLmy*$I?%p8W;R6omrW~_o$vgsqp~|Tvx_aeVN(MJ(5tCT&tl7lb->bL z&s5C(Vk+OL&9>t;O9{dBF{%qaEO$IV zv>!x*Ga}WfB&%{VxnU`R>F;g-aq}-PM6T`+1B%6<^tj6iUP-m5bf9+=RR4pYg2rr{ zr37mC`h49p*P9mG&Y}c37`tV9rq~y6ndm7OrU1`WT}F2X?v_Q$YsH{bq_CcF994-# zsQJ&G+jo{N?>y+n71xansHHc-xMG2>;u-iTC;$NOo|M2iWDSBIL?2C`f~p6notk`0000R1tyh>M^I#_qv{QP$XrggLvOd7m!M?9MzFFYo`y^Z8emhj(nv z&MH@wAp>JwRUy-zK)tsfn@+-UZl|( zT7umoary&@;Oto&cW3Bxp*q?DA-WPrA>Hx(-0YX#T9+ErYr6N+8=_3c0}@aG`layq zN0$!)qX#{0U+lCF<*WJUhC+sxnbha4L=+=ZR}5>LR2JCsG3^VgcA{F1 zl`I{k9ksc1ZQRVK zs9{9@mEvq55&=BuE5Rz;SH`(93Ah+Q>i_(-uGBK1U6f$$-P@7 z(E61dDTey6mx6n*{JdiTU@i-Y$m03`yu#^%@1Mr^mB8YP>BfrpKk}dvmX*M-&%Chl z^X>1_z%FK#(CW1d&HkQIfJ|&a3Ab}S3uDRBV(L7#cer#4ps%S;ym8{*ajG1z0Yr|u z{Xpp)Y_$i+0q*3CF-`)`7eyD~@B#Cz2I%*C_Lm^JS}}PwK;(S(miZxq>)*I$GhQa# zoxo!sB6A1#DPd~L8DH6BTi&P74SqA!tz1T%l3RUw>0QI)JDF~1od#ln~EgM8``UT%NteSRUuc||C zBnU9%Ym+e8N}%_NW1xh^KT1|xb;RS-5DZ*8Rsxg*uV%r(BToac4{`w@QomVk$$b$K cTk!q=0mCXct2Z>fF#rGn07*qoM6N<$g2`}%OaK4? 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 a5d6c1a7a59b8116e393bfade90b5891981070b4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 813 zcmV+|1JeA7P)1v7b7Rs(o7gnOg*DS66-E$$HkCr=ieeTj1et54L@9!bl#=W!D4i)HB3-mD zBK|A}E3_q3jZ`XySZ7>F0;NuAp%t~swa`vAaemsAnaSKauZzi~iDz@Z?>R3o@Aq*S z=YJ;zz_|-X*F75$(7BfFY8CFvt!#@leT=`rD(Wo(#p+rE5BxZLniW6*8EdNR6~6g% zU6f*RCS2RQZ?Wy0b&8$SvTJiO;lI}MFaX>rG!-oiw{H~M8ml10#-{6slXoSKdF6}U zzj|z0Wy%){3Wx>Gtr(Y0qc}Bo9ARYw*+gAcqm|aUnEQ9r;m&Er6R9{RyRj9f&4}QK ztYe+X#{l%Xz{2$p7vpS50&I2w9r`r-`sFNud7MylDk&`m0A0t8yl*#Mc-|xO$9I6g zOe|Mc?Dup=A>d3-ncTx&#%>q^(%`dfZg;_ro*&JK{&~PT!5#NrtgZLNIfm#<)8&E* zFAn(aFOoeu0sTg(_Rd%F);6L>F!u6u#~{8~orUv8Fm>_F7J$%2U%G&0>g%ntfW-- zec$&Vk}J=g8cHOS-9Et0s%s*2!V{5TuLt1}uGy0fVQ`ky0sQvnlH2_0r6`XaLVs`e z9athzww-R@e{A1pB}yHO`%?`mq5hGW?z`%Al-tgfY^~QLeQIyIH=w4criPVln^##{ z8ajX-yn47^O?;G>jZPlTh*-XmYuriYYEnwSB*aYZ)|G?ji}N<2Ym-sQ+3|VzCzWvm z<1sM&XaM&8L{W{63FTzBrKg3Yq8j_8+tS3Y{qxj%ic;ePrNV@+ovg&j-+C{jM#a8u rQ>r~%IE&P{qDHY?HF?>=f6x90pDlLgl6o*(00000NkvXXu0mjf1J{uq 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 57b2c9bbee26fbc31ea9e384d8ca8c44936db89f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 879 zcmV-#1CacQP)~9or#-tqXwE=trZzs6*sn#Qc^H<9#z~Z znvGTpHdzRj*EHGvpJk|zVmyz3{er~ zRJ4A-qSzE22f*LtK!6Znr32yxpa5V8kOUDc9n>+g7yva{Xara*@Sg)>UsD<0H9F4%#7+c6@3i;;3lqVlFP?gN7EtTWC;%2# z#Rt^ml^V7kc?m2Jsjg(W_|fU|hYloy3n=+GP3T~(?GFi-licd6WZ5MW!Uk-bp0CnH zdV;G!!aw|$6jWZssSq8i``+Ff`{ua=Pdyq(|Kd!Z+7C|A0as;{qC>(E#(S*jb* zz%}pic+asr#b5aDQ=TphvIIU13}^V}&P1 z7AI2-Uz|81a{PEhru0d^T$|K(b8`s#qVomo%#_-Z(oa63UFf(JZk5-&^^##mem^L( zQkLlI9`QY+yJ@_a<)q6fgQ!Q1hAV%kJr^Dbe-7`Zln}2uGrz_1J!{)7|137+9bI#E zCp@g!Y1DQ{b8Xl6lSN-SjJjF%k+ErRSGv>zbOO`Mn$Eh=J2wY_`qSCMt47NYttvkR zpdL=MC505m65=&TyGYK@|CmHX65<^qCsLH7w@vUlBc2OY&dz#+Nb$0G;`s@YvCL6} z$U?t(;>nmuPA()h6rTVf4oyT-h~#c1JSmHa_=&s8e*u(ie_+i17}Nj&002ovPDHLk FV1imcqT>Jn 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 4be832c648910992ec977cd567913fc99db14545..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 855 zcmV-d1E~CoP)KUdworlw-BFPg1P$sm6{!i*7qQZo8dHS0D;13N z$%nr9vh_vEth9X*1EygLLIXh(icMPr*-6mG{t20q(4A~%=lHOj&8na9}5Wptd<+DbzR3cxt*Ewh4m34o@}VAfhVy@PsMiN2QcLsHlbZ=iN@H^J-wY%Wz~Wu%moQnV?E zl&<|PvNT)+NkmHdy&`3)&FwM(`N?Hs)&)@e2xVUUoXm*_(C8r9Wtr}g<)JKd+m=go)IkZXy)*G?=PZEl_9=ml8wVe`O|AGu6;sJtb0sn3 zXD+({W}vAs*>m!ZR}VcC)`OK3@o2q4QWn6DvXY99$wCKH9)OqDB;-05bL=?}a6g5J zoDs=M=s1q!WE>5ca3Jk{Zb5kKsFLcrV3e8m_9+kUWYCB;27VWN&La=c3~G!tUWc2d z$>vVxHyepoh6`1ZoFx0QlC(8y)c2!})K!XaUMePSME>aSm3*uuk44sOTM5x87pvc! zgYB|EAD9>x3mDb(;WpHey}MZCMl|x#w|}jaqMR%`U5;ZMlw(ppg{Lf1z59wq9nZh^ z*74}6(^f0-y`z8>UYaXZzYtj!xnSEivQ(%JRL@Bv%*mqTIGQ#KpD$%{AWf~ULk_;(y4AEptLo<`ScaMvi$)DcMJ@lu9EEl#Kq`+yXr&IxC;l5FY?FvWz zxh=}_@^8V`xBid=!`l>x#>H&S#Wm-2#X|w`_fknwvCws=AQEnX0FxYFA8RG8|$;q6XG%r=c zSh^nM%kI`Ly9DYGnU(x~U!>GGVZmPJIuh?c$pEM(ME;TFoG+3E*zGC7D6=b#Ju?b8 zXh3q$f8Li0HCPTxFy^m=?SKysXi&jayj!K^n|jMBZ*FwqCPN5yJ}7~{caIKLwUIgI zQ34}VV-1@-1Pmj;K>M@8!?NPh>g<~?5a=>^_UZoWMv@upqFVwhC+@vd3$PdiL}YE^ zUpH|p;d!_4JSDKUV=}+v^Q$i8GifF8;LwrA8^)Qxgn+#_u-)cCD&`4+d7Q(9e}YfUK>b?o8c{&#~h1?Em)xqyb(Ibn|#eaqF=P7ftn__;9!x;sMT`- z>J#Un?;|s3`X$^euZYZh3D48)nY7`I%0ax<_tDf3jzV1+K-KG)K?!TWri^9OC70_N z(D3|n3Q#tjjDd#YTLAPx+y_K*^ZlW$i-?$n=e+=t8zr75sa7BfZXZXg8NeK5LsQ!P!!h0x$q9-Z^pKELh6!9z9p95xv#B_CqZ&{nIMI zj`RjmE?4}-CoxyOig9>&u(XuqnK9NkPzYV6zGK^i3acPu zp+)?B{7}3KVixL3k%|63wxj`C9Wd`lu6u`n^8w6m0_ls7!(GxGK;yVk(!cV=KW`EL z{UYF2NKEN={nW0k0>JUUG*g{TH`^22feg4*%-u$(VX5J3)2mzU&ga~-O%!OJALksR zB}1bT_nL#zozVmM-5#_V5i}JeeH+*4vfx}Ys~4c}uhJ#BVnq0NOwYFgfkmh7$sv|w zMo-5RzB8OwXe;F?$X^`vU|S|ESSdY_{-CbSP5Oj~#=o*#(>)?2lF0zwum5UFqc0am zE*1{I8)KQU@{&=w^2@-i)7%3iKKfK=02nL}U7#U15`T3N!0EI4^QVs3 z?o+h505>pejJ&DsllyfL;PQ3WLZ!6wtkmUsp0~GX&6c?&W*AP7L9h>_%KXGT#YgfZ z$G(NDZ(B=r;KTHL)2s3N3|eY+EoK0iySabsIj-(J*^QK2NEQ7(Qd@6A5Bz~tUdR@e zmwd zO=w(I7>1v7@43m%Pdnq!XvEY+Ce^{zI;~I}qs$a4wzv{|W6%gOq!B?hJ4LH=t)SvU zLZL3S+5`%XorN2PqQ;q(;zAwh!XFwk{Yg8f+L?6H%)QCX{aoBh?YBAbocBDOZ}Bq8 z#@OLe2f(-ij8OoBCQu+Z?xJC081MjeQziKg=}rLj(3K|#+@>d50m@#anMnd^jENBd z(y$MhhFlN{U+_kl_~hA}yyT%={!zS}{;Vgsd*DO800ZZU^j28o*FPpUTV` z+p?e<=X;h5X}i=>ZT$O;3kFeeeQ)*omd<*AHk;Lo!Y##eY|F_`-hK|yYTr-A$u7M< zG{not0JXdnKiB0)%b{6Lonlo^R4Q*R z1cP((gY$&tXp{B`nXL=k>Xo7sykP5=sRw~M^PbJScD#@6X)dNf-Ct{cwPZVhLHkx0 z>PBi(>;CN5S&j8)6i`z|lJicnR|)yO^}4QmAMM%hPyT(U5(?J`>)Nx7cgywkJBJae zo=MfDCaKyNv8Nl35*mN3C+v&eg@oNdoB$DdXh%8dmg>43NL_a{FzqMDE~MuIwkN)E z5Rq!7NaiZ0@xpmBC(vNX1= zNV`=hEsPys#*@zakEsOspa6_lxRos51<*!}LiPTXXO&<0MwoGb=;KSfO_gLWeeVTt zZ%dGg#fl3vYfCN>05mLaPNlLRR8ewkSbL|{LL<))Zudw;3lINOLC4&7sJZ)zqe~f? zEhl0i%cJkFisbrBAckyC&t^r|r8GBbV63}G9Ij6}0O}b7BRetY@Ko|LK$VFEnU%X+ zlJDyVFioV8srzb6?uZWk z`sZl*J$|J+N`YWAqW3kbfs+m48*L*15l=S^zPt9QR!0s$URnwL*@=z^yCXLvq-p$%Z*QysNn4?nzjye9?zUuU)P znq>vWJ1R5g!t{5h4u1iv@)JL_JcQi;Fs)U>vO-&m)P%`&>zIk;z6m4{nUq*}r^HNT zVg)WSa zJB>!GF_F9>tW+9W?b{e9F%c4DB_Wh2tx{T$!VJ!x`sN)MGt#-6d(W41zVCitjvDWJ z)k(8?^m}&uI_OR0=*`vNH!B6r>Oa4_9U~M(@&K4N@UO^Bo33?@h}i$TlK_8=kl(1<4*5_WLJlW{%hR8cAHq-F0tCAlWY!ftXQZrk%tLa767O)Eux&PC|SJHNv)xg;&YW>F+0s!@}Zo>SK zUP@=D0k#&9oOP;=r+1&o0~8A6&Tptle|$wEK>NNa^*e7lcZP?a2Y9xOs%}%)dvD*j zFes~g^eHgioPS6iJo2rFu&WG8IQwXl>A?YlVZW7aKM+3i0u;)C64nwI4m-^=&)u+< z7I4$a&lwqk$h1aQB#iDJ=$;SsL=lwm^?`HaYRmfptnCIR%a{7njh^IeDj**9F>Ihr z%_}`P1`NNLS8`?QoxLAD9v=RIb>a=zu*3WCcu3%V0^nLD)}5)mnO@?92imD?ybAL$ zG7fC58xqAK)PVJ73qY7MDE7cgv*eV4>wO0D-@tlpf8rD{@tP89%bDoUL(9D$M5jlU z&|I26X{`Hm2p2Obfx+aAYu!6z$l7Pz?1Zr{Ivp=Nw(5;}?LqsCG zZ|XCVK>L2yH-Ph)`s-yl*yL`xtOWQ)XX`!4K+(F86On3l@ra0gENqMkozM9HtZM>Pv%4_IT4XK?fbq_sS;#70QGtql&oyq-dfnH;!*_+I}^ns nfJ)I<216I^jeL^CZ$d11@;kWVw|A_%ItK^W7EB z2jgA3l%Z1jQH?Ge_7uDKzug6#rBm-o<-5Lk-#Z@2?=%7|twr@NBbLC+Rx;hHQD*uD ze|wjxIr5P{yUD$wxhVH7j_J5H_964YtMPjLiaJwT z*7BuKEV*h6#E=>zi_P3y?IG+yQY_t^y_0KI>_uwCYQaJfxd3JyX~TrEbGqdNY&(PI zHcxB)u-+U4@Sd}*?3>sOyv>c01;^{LKq~gcuB(@i0nZmVjOOF_r#PQU=w`oDVBM1X z=@2{|9YO3UvdxHdhkkPtE88iLo`pUm_FWh|A75&BJ7mGF`jx(cLiX>*MYv?a)nvbI z9xW1D^s&+PT5o@l)EAku^Lkz*Mr${E_f)g95^L}I(p_Si#fUT zpLd~H1V&H%ZWJDP$8A2gP)boH+IZHsesm_51t4+468n4!v$y`G4FQ7@t6n*@&D}15 zQ?T&I7+}8k0sg{>@<7Y&!lSBjCxNLXhV6eXKv@8UepLL-izjJl_~_k)&GfiYsYKKjefYg<<;ilyvRvkXk?o z(^{5-Ti!w&T+AqtqDP9;YVGErOHUs2a_KuCX-<-)Dd}3NoLUANfIZOAa`EHXP`8`Pm&ZK4P(h<0c%ii$YU%_p4?zWA_o8@|Yp zf>ie)v-71k0U3(S(Mq!y6%{KunKE4OgKf~dCT+UqCb_xChopF3J~-cx!^h#{Bu$yI z=CtRUgALhN00=iY5D*0-`4;g!-~%`do859zHX z01xIuqcI@iHwpXUb5v9+jdv zfJ667{=}xp!B#6++WM!CtT!AOYZ(`}Zylub_fsBpfI)A1k#eo$-iGpQ0c#n)Cyd2L zW*Qyg)pki2z8TeR*CkvSkZL!irJK_#WwR*t ztyFJUs{V$nYMYDXXH+i+^NkaGHT!AS0^?9B^lsYx;B4 z%D?)F(^6{XS%nQ3DY)j-F3qn@N&Q(Q?K@PsE|n`I(pRnnOxC;qfmq~c!rLyA%4^~A zjFg&D_?v5iSkyd{fbqaH6Rrb9hT1;+19k&5%MQQ?wmS)37oOSqTGFP0`}-D;fq5WL zXO8d6QV=B0N-BhvAF@;fNka)qs_N}UmoSr(zE4R~nE8Ay>zL#?>`4QdG6Qa~idZU6uP07*qoM6N<$g5|EIEdT%j 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 639834612c846903283f6ed6628f2c79c3fd6a1e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 852 zcmV-a1FQUrP)1v7=A^kbt&`HmrW%{6C=M}V3a-o|chR6ILKaRZph%VYN2(T;23%w=byKKd zK^HE>R7EJ0`cp*v12MOv(9&vybP;K2rms`?;8z^xJ(e?|IKT&-o4q z@B1c3g{)W9{bqnFuJ?MtHb&?TwN`-~Tpm1}h}In@`<0F(W5C#|5=)dO83VeSkwxA^ z0AiW}<|UY-O=<})WOKnap1A5&g#sq)@+CKCYL0#A??S}?XT0X{;fJXei5lZOM^By& z#Y^51^Z{EI@fD`7jJ$QLB!Sc}0{W@27@up!0|YR7%L7EYCF|A}SfnTUVqx=`zaA>b z{**6f)TH{{s<zCKn4VKM5y(OT z;I6!Z;83FYU-Ksy0iGE(QJ5U4l$I;y69#k!)pWjThm*S3onjO+pl$~Hv8-?{oD?9zJ?wvK{&E&bT;3D@~|Gk$L#P#-@&bh7VEWB6))9A3Gw6c&P(Z4Y0$ z6`co^HEq9~JYdTGU=3h2j(UB;%HAGd4||~EL*8^-_}q9Fm~R0U&!mEFpYCbz^nK#~ zJVqTw-97??gFx@|eM(L}Ev!8>eJLa)E<1)DAuyJ>asvPgl;H2N*4UK+fZ6Q^n$H2YDFZOPUkOWv{imJCxyJ>2qF)J> z$+fg|qwQh=CetW^F8y!lf!t+j>Fbe}3?gF7 zGV-~Iuk-niyb_?h;drT?;@vF2gc9Hn-LaZPI=#pN5s}5kYp#fl$ZwepA|lqde=s64 zlkdn^Gw8WqxYrh~e3 zL!wPgT(s%}oi=XVV72Mnn3h<@1W^)!4j@#mcBUP$@6pFQE?x^$@8yn@(@;@U~vu zl%z^!OIHK1$LMHl>~L-zE6H!jWk!7SMVMqa0=TghsMH|gY&$-vd+~{g3zMNL4XPR$ zSoLA&-D_G{sultYmE={7<$=NGPqy>)NrH7Now(ffW^24*jRLcmx6_x_2TGyUl|6K@ zVU$7(XD4yABcFh6&e%#VY>GyHaOs2EtM~lC&3^`MLYv#!;AHFb@B<;4nrMZ`~U>1I` ze7g(Z?fYeXoXDb&t^e@-l)kRb9W3}xv834O_1w$uY%V|o{a#5ksPu1 z$ovi$!1>%_=76cxnF_#ljF|2v$^`!&1vqoXLR+as6t#K)e*_k~vH+>qT!?xw;?A#+ zMK-eESKz{{)(9Ry7ug`Qauogfl4pU>+|w3XJud8iZy%ECXIE8~a{UEuxrC%>xf(wx z`A)gKa|%h?lRr_BmZh6LMI>p?H#I5lE`K^L3X8Ri+H#o%@!mnerWs~S<`tEcDn%Pjfu`Kw6H8mQar4y6RQOj#={92Rru`E jl;u7;lYj0beNp@eu20-ipVE6~Uf(?YKCPa%s!3ChnPn!ZgKpNQZ z0|#=Ra)E8K8KD^jI`URfrBJvs4Lm*JSHnEY!*2u6Hf%KZ%y)&qfdgY3&7ZMrPg=08 zq)-BIYqc=uK&SXgO*1yACa|ooO*Opaw^D?$U?xb$2V~AgHo^YTswC@5#0mRg;;bHZ z3FY5+0tsLJx+hr>(+Nid5K29C?!Iv<*S8_6RIaRC+Ou%tvagGQ2(9UseRnTU0}wY@ z^Lm|fW3k-{K-pNk#Co%5YzI)gjb_;+UGmy;WrZgFz1tz%8sSAt>xS+?ku1Rh1i{)6k?NHteb%0MqdDo_0wVf z`n=X!xvPt>{TZ&CXH!%5N*t6l|6YF}v7A`FQ*yi`8I+u8`f|42>8jkSS`_U=%5&Qz z-#lT@e7M&e!K;OpnCmyXRW)$)vvqfN(g(a0wM#DQUEQ6{A|g_~UTP)-KeX5?XDMO0d1lUP{Bp(GzWlHIs((L zt8l}X3p25r+G6!ud_LN&O{7kr+LAGlNV3Tny1V}DG(ZFjKtD=X*ZLx0bgQ}T;nYTl zh=lKFTD(n7E2W%sB4Fn_J13V)$I3Y6wX3v9H{k8w)@c=;W#}?(|1^S2w>W2rTw+{c-BToST=D=u-gmks;^vVo6_VwQX?N4{byAa!t7k%0PfPv$S+D+;X?zot z!aGEs^rz=s;O3gq-t%u>_zd;Hd!s-(Z&beX%uB7%zpV&d*@n!?xPBJKNk`G`n@B%QTE8I-EEN`PPPN8WjZck^Ke6Q^JaxEG%3 z_-PoOa^Y4F+Hu>8FIbC=iCjM>$+c)KUbNOKsb)<4#~ztYHMi!6KuJuByML8S`(`;F zfD&ASiw8?8(aep45-!qHow`RSn&qcK300GcYfU;b$R3aX0WNV?X=dOLZ2$lO07*qo IM6N<$f@c1SEdT%j 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 962349bfdec5503429689825577af262921ac6c3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 828 zcmV-C1H=4@P);MYf0)t1eLxntVNLyloX+DW?F4xn>Lx4kC|lVz2hP?>BPG^=lt&B&;Nhe z$Nydh0DT7xZunLpKw~}YuQjGxC8&`#9c5=&N6iIbu2e6O2D^vhwGJQ;rz2I`xM9`h z4OZsney!Bc=1tm9ZrFI?M~Uiv@ueNAX(#}uX4f`q5^7~o&di2&DB5-Eug~SqtX1DP z_x?f9>aIt8y%2v0r+v736~o;DEi^xOh-v}}MRKyT9$ND;fSLNc>P3gEkVkZT&0JYT zM56wYHIUNPsBVi}1ts_tc>iJmpgIjuT269gUOB)ft8$JgKlS47!h_?0g`9*kG0=KK zDuDJpm2)Cp87qz(e6XN$&Y7-CY44@1+-%XKU?6$C>!-d;eF*pXpmNUCzFW^uFOL#* zdw|Lnw*J5;@+}xLYJiw^qRKm&Htcw_)b33 zUNqEb3X6BW3)_NBrX2OtSTd4OJrK_;W?Lp)P+=G^$00KuI7fbI-c;0)xY~v9)6S5* zXSZ8bRxvLAY@zUQF?w^c$I(=<;fF$we>%ki@tEa4`EDLf|WcomHtkl$Pt=2S9cG-e) z5_8wBH44S%G)78~Q10~6%8j{+4DxU($qj3*IpoZ5j|~@^Ui)`TxV0C|wSjr>!qu-y zZkvx?IS1WaHM2&Rw-*#8yCq(l6|wakAriAN@4v~-jJ0000C6`P9 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 2eb4456cc780a288fda6b71ff3ca99b931c51b2d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 585 zcmV-P0=E5$P)>Nh!OB=PyYTx622!q|kdqdsU?O6C zY`-77ka~b3JP3L3LgIs!0Zd;Hk+vjd7p0MO?%AFTaRBBC085+0@+}?!{nZ#{lBAI| zk}kVDr6OOCFON@Iau4&u`Bx3c7{kY_)lM4#Ej_Q+6-Yh8Z=k>B*GUKAX*Ese$ICP0 z8L+!^i5ZYC-uYR90EnFzb<-`4*OguP)Ht&3V|Fb%C>5PO>Cx+2p|}NS-`J?gV=)ir zz70x62H<0TQ-CQh41ETce^co0FIr!Wq z)xD}&RR9ok+5ix`BC#9JkkfQycE+FiSLaaE>t?%f+t<`R>!|U-2%8EfD`o&RA>zGy z-TmLTSAiX{>=J&-=7C$F==OWd64&-!+h`ToJoNh)^6=0kLjn7IB#T*5XBMEG*2HBJ~;}B+3L_Rues8tiw8w|~g@?NH`D5Z=;plA# Z{Rey_OQicOEv5hf002ovPDHLkV1i=g!{7h_ 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 e5772a672f9a63d511a03cd14cba48df73df3662..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 812 zcmV+{1JnG8P)Nkl)On1-IBH1q<;PWKD5!)^K#Gm-NXO<@41Ze zzwiNY{sXUWyEY)8t(lEB61#OO-ymB$!zBLg?gY45Y!*ns=rxXRW&kDlp=4^?JLamh z+pLsx{?psQ;nd2H&9$@0>|@QooFtWY03Qs1^AczMhH1c3pIA$Wnni;CTdm=A6HvBD zGSA)yb9kU=Vo_p8H@9Leot2K0zwqQFTQ5l6j6QXzX0%lrC%R+u;nrjJ>a~VMdA&GS z4(?H~2|`92GnJ8N11I`10J~7|-FPYNg|#{NK?{Ww{z7T#*F1oE#NjSICp*rq_k97d za|n?Nb}N4A-5~(?5K6>r4=ISt2ORHP%UI$1<@7syMgR_H)=m8Lea!rPr*}v=q>$rkwl*0<*&ZC zU?yImHA4m%OwQOyZvL-(F;7H0sSqT)ZQ<>F^-XwX41l(z{m9Jo9PDVVMF`%KQW{s)bzRqu$o?Gz2qm&E!1bC94yDt;WaxMT`~j@f z9R;OVIa7k-8>=Q5U;fO!(|0web&Z8)^pVL4F?q0000P43(XaExcZC)XO5g_}>Ap4BQE&%nc>iptZDLR|u?6uJVm%UC@+kLF@2I$vP@~#&R zxZV{tB$?U`%t5S1;qJ5m9kDuJM}JpjA48EIcNKk+9(EOFv?MK_o~iwzsEP7#?DW_H f$=?+J;Lm^?$5Vh#T320I00000NkvXXu0mjfxI%Y8 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 ec47b899c994843115098e2b481d4be9e3545bd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 270 zcmV+p0rCEcP)g#Y&IE8RMH&I=ZT-N9#$G7Z;2?CV z!MD6x1bD!iYcNdCSF^9tY4hauHh`CC?40-Z-=cvn*3{YeK2>8 ze+2U>lk@GCfAh=B#t>UH24K-BvC9r%eZAQz=j37B8kTNI77Uy6BwY4=Pwc+n50F}o UZhAxmRsaA107*qoM6N<$g4SGi=l}o! 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 3f345b563e9eeed887691ea64fd476c58e9eade1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 236 zcmVukXtLRdY<7&Yil?6l5smS!)4=ixyV{H|i zEVZ~qQTSxJvPvwAS+^ZYlHpRc4b{SFksE~%lY85BfJbk@bKKYrf4%(Tbt~+$){Jx@>H0 z9v!U*E>kPOV&QLk{US7P`x)X2@FjRl)L3A-;}s^}z6-}5I>m6R*COBYii7&ERd`uB zg+3&W`T8Obyggs*z8c*$1-S7%TAE9JE`yu<`gW@2bNRjVnA)?Z&*lEIf=!Xm246;w!wPWp`HmW)LcC6j8v((m0$X#-)(gTGH^FsQna8H2~_{aHwIgSCq bjE?>S{e=)!nb_>V00000NkvXXu0mjfi(20( 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 44c4d0d498b2ebfd6caf104f011430dfe5b1d362..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 771 zcmV+e1N{7nP)-)V`@Jcg?b! z8h15C(1l>d;+xnmB8bn0s0B?%Nll~j^76-I($3t^#mqxoUG9(XoO|xacaS8dU}7Ul zvH+yz0pJ4=0SEvKI~0J8h$~QMwbUk6iDv-FZCWX+W})l<4*=N!%mPs`Nk=`_H&U(RQN03dn~zOR4?0F^bcZ+3Jd|f@+2ZE!M+C?0$2hh zw{b3pwgYGh$>~DExEw%JKEP9k2~3drd00M~y~*R%-#{v~l9@dz#;0AHy!)E}^O+Rx z94I<&WVukn2$BWI?n_UP21jE!C^3x zDZI1qZ-3U!AASDMd$7RXZN&e z0fPM>xds;872w$+fB2Cr%~H`{g2wRTyVJ=QSO^(AJ6nsq`o(wFD^F{B%*=)68BWF} zXvTF8XWqhPlMHg|MfEYgan$}7+&OQ0>r%*vAZ@7aw5@LMqSO@9nv+wm?Y$}K6O~oM zi|WC5xD2E}p3qgPtJartKM85w>$fDS`}vAa$%oo~qjz(2j6=ZJSTHBHSJMXE^+B4= zU?IC%uSc~Qb0kp_Bdvd-<4w&=Yi?5=q~4jgms(nr_H|mausIPt6w!cMd<>9Q)~-r& zZE1c*ve0jw1WX8%DOgyo1(dtlNXk_={mO$H@Gr*&ZJJBPG>ZTL002ovPDHLkV1id3 BYq9_U 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 f70ecb3abe0a34f98debe1b004eb08b12c98673b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 887 zcmV--1Bm>IP)I|X#-uh;n6?-_SZ&e{l2{{$MlW29fAFB}G}ef6 z^We24CdMpP8)HLjLOaozka(ay2$(iyAm9RpvIS%ZcJ_NcEbH}`=Y5{{AKi!E42k#v zphU=jbn`Zm>HmS%9n|H~b!8xcSy~v6Rp-lqj970})rLVV1{%PFlE?{~Lcju=gp(qd z*%bni;C>+|5^&gTdAuyV+9n|%4%uZeCHnQFxLmLD%HZub4lT)5>VmBj+N=Ys2_K~^ zV1&lW)zNY+0M!g#3me@gLq(%!W}f@y-)j(p62OXcD-EhLnzo)IU-P&fxLXyWwT&xb z|HtPlrdaioIrn2=-l|Ag9$u{a^!EC)Ppq60V{voLo_a4%xm$}zC)9i+&-Yb4NaVNu zT}A86)|x5aNgZC!$fT(ViYpi2T@%Uml)-}(Hzy{iL<&;QO|N1JE$x+v{x@M(!=v#?_jqm~nttmr=t z7iljxDh0nY&TkKQ|yw=xuTui4JdN{KB-Euw&K_M zu{BbrM#JrEfIY|m{;WB-CK-FYS(nvM?GAtt1i*TU3t>YvU|1~Pc4N&XpYNDWq}(Rp z%1DKNpNATlm3g=#xh%PX6<18Wv~9zwZ|N?vGQrSH_Q|xNkpJuzI{_f%wM-vl7TrJv3XRa0I{>wWXqt=#1!scW6rfAj(5QHzka zQyVTW<<9`DcMJh@nWN{PdHpKD^JPY&Lac7bwMEwITfBfU#P$ zpzZ?at5EsKk!a|~d!n)&`)d%L`FKEdSYMII!X7zGr3=u>>pS#Q)ytacEojC;7Z+o- zf~u--d0yT_qhcE=ol>1wU2q&x&iL;z`(Ogy;s!}joq9G5+oR*Er}dKKpt^WLd)oE1 z>NPL#d7`Mhy!(hh-O5rh2nM8MqRZx9h;M!aL}7YtJLn0QcYQGMO@3zpl7-zF5M|-c ndusK)F7<&Z%d5Xbx48TZM|mrzzUzGF00000NkvXXu0mjf3B8d` 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 7d970234f57cdecc7e45bf765ed313ad92c3161b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 890 zcmV-=1BLvFP)6U8pcMp@S4V4`@4ocyo`72k70Lo9~?a<@?V4 z7-HkNVF>`RHUVn@0J9D#kj-8!oah5e0M0sEsTMByP~%y4 zifHE_S9;&`9T#56Ak&f!I@>Q6lLt#!o~PvQmWV0$Q@q{!jiW({!Iqm>)!xxJ?AFuZ zU`dwo>wNprw)yQDKtzs+Bql2VEZ#_Xp67&V7#vKE*?+fZ!Vmso8&H_w08_NvAH?=jCdf)!o1&-%i|OKByIox zqfF}OBHD?>C(oWM%kkR4&uP=|I_j5b%BsXW8h&_k_ouieho*b(@fTn6Bc%mb16K9q z*3XMWKdF`T9i{?#J9BX{Js+F+dx0w|yy5+vx-q!{q<+4=$2V`|kdXo3F66A36uoXB} z6B%s-2h^jkvB2)S^h~NLg^1+yYf==Mu3qsHo)V~txG|rV+P@lK2N5wJ)=f>1h#6iy z<#|>LREs*&0;(mlE0f{!s>rQ;P#O`*uOvKAm2H~}_jk`hrE~}|BS|sCby*QHA9$YU zsQ3v~3!Q-4l>sFSHQSiqlS7WB^FM<_LnNJkFb*ot#_xYEA|jF(lkmKM0Xq7HHc@&K QWdHyG07*qoM6N<$g4?&KI{*Lx 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 70eab1f3e8c945560bd8aac429f4ae7e15401456..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 819 zcmV-31I+x1P) zUuYav6vlt|=B93H!FAKbeW-EU1Z_*GEfkSfldZI*f?}ae8v4g9A}AVcAQbvgb_ad1 z_+L=a2Qj|sjFwP*$U~(g*ir=9^q~+?$RMFRB|@f9lG)Ad93N&k+xu|ge4Ou`?|%0j zHPQd_kqn)qmV5r}K8jA-fW8>+@Sz(>OpOMw#lnx|~>+7u6}GfM*!KBRqQW$9C?c4A!&7q&C) zZR#>l`I#;9n;`}zfIja1F(kDxeFzxcYip8?Y`a8Eaw+*|eJ)F*YtkW27#2cQ3fD9xxO7i8G3U60x|3hHSFj?51%w@g*Fe6W3Pen(-lWthqH zIKkiFo>@#YdUcaWUK;ygeb245ZyzF3y)D)2H^w|bSsEA$o9Abri#(vmLPTOE(QB~) zSe5~l5WL-02egF&)bGsRKK>VQ(gpckWJY9Pc_ctPj$;TQ18@D9pQ6_VgMkYJ99z_qrZHz^_5-O!*)p0$SA@8>``u9&5i?P zA|=hi@vU}}3!lBz`j-#=wU1y@SuW7CZiv^m0KM@Q0KcH*jeYOjEG9ac0J!&rVWd>M zTm-QI%2S3C^+SMqw+Qi_2_+D0d{@?S)rZC17$6rG63sIoBP%l{B_LDHuT0c^g~i>i z$@@l4X0l=b;Z?7{h=@pS`7uw#(~h&(K}4h;O*1O@MIKm|5nrOJfLwFl+f(jocm#${{bV`LSb#O+VcPa002ovPDHLkV1lqyd=>xz 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 8169e0766fd617850ef249e6919e2c7715e8cd9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 799 zcmV+)1K|9LP)@2%14c(S3tq0sqQ7jEH5Dt=*W+!+tAtp8Hr$`!T0+9nA>H+_Nu>*%{ z!r3O^VVig%GbQn&7sQSz>BdX-(8LRNqlAUFbXQ8s>@Yj8hn=OZ-{pJr``+()pXX(q z|D6y3V*`wCc~&4mdNX^kU6}DLUYoRZlwRfq%GQCJzgfTnJ;GS40;s@jr2P7p4z2EO z`BkgUH8%sL>5Hx;%?&bmOPV#=@MWC5ermDatP_F);K8ay`L*yx0CU$U5dPW*AyM(x z^U`0%jVUtakvX+@=#1S?TU}kPT~eh=ycLt~QG(ax)Axz(Ni^|%eTCC)w+3qW$n?oI2)|Li?GX$SGl8*dSc?ud<`f8{IV6KN8%j`Qq+U;K{fgcF08uYQ&u>V>*Dtc2!ZX}?$gX!6H# zq9Cn==(qf}v5Vg(raVadCdiQojiEb=w{x+vg#B^nIU=LSh3(Hwpb#Cg{#fZvXs8rS zpx-R8%_ni*;!%$Q97uyP7Vy2+OWFi7VFxK|`I`@}h6^VQKwSwCd!Kq$AG)axK=pN@ z)ZD^x=k{?|2~f>$uM}>t@@GFFBGOowS((iZ7bT}nfaD|=o_p+uo70GhNU3w8F8_)= z)mjO_!)u>sms1C9KaF7lba$l=XVnG0*L9g&6OrikiTN+(Z)d=9R3ni9re$PLJ@C(h znY`?I@Kz&}CAt4AP%?e+AOz1kfhiA^Jk0C_dGjZ^Z2waRlkTK#qmH2p^tdhSB@q#k dszd{h^B;3hM?PLq!U_NY002ovPDHLkV1lI=dW!%6 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 adee1a63dac8b87d57862f233107c1f961f39cac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 817 zcmV-11J3-3P)RhGNuC&1^3lbT18NI;gl(OH8CIa|J4-6 z(9S=ILoqD~ih^##tZOy%4^>3nkhO7aYed^*?YbskzI=}tNn_`GbI$pl!}Gk)3x{3& zuL1y^|G>Z{*9HW%EN2fcZERyzc8Sz=jQclD6e|J6GL)JEDR8&*NEuRfkcY2BQz>8a z!ZoKBgJrhG;k3$Z5YIixT?A2j=N`iCBxBp4$A1Hidih4IN`Gk{$) z08G7~Xn=q)d8oQ~xAB^ZOz%*CwyJlUMANgg7yseme0?g$(l$VCe`M3^GyRY*IssWhRbTRzmwo2GAv1_?vkjcY8eA*Zql6w4+_~uC6_XZ@5yYZkJ zq@DvzFOud8@ndK8K^bWM5J^EQt`{{?0^IwMq*yeJrsaW?`NRnL@qv@8)}6B8;#(@# z+?ZG~S2|Z%>$ogflvd5d*|{gpnqgXv%Hs-hUg(dpH3L^!?6F`Vhv~a3>kdP=Q7sal z+%a0YJGrx*gI|q6zmGkh+uTcF(E%j+XHF=YZ_SK!XFi^>K()=g_RFq6cF{f@p10sT zky|(I&Ck1peII>hq4v@}eWf8H!?1Pm7bD8w_(u0hj%`@qWPBZb26TCpn%6&SC_-{V4gwZyxYn-U8Fd&v>xl0)?^# zuHM(>!OCF>?!W@(WZ7Za!0EEuiE^`mY8|g zU7rO>QutJQ;uW3FbW3%h_uie0s&d|)QeA^W<+}BGZFnta8^B+T0{cf3+mI$Cg?CEQ v>5;|Tk>(}o1xc!GnLUCOXsN>Z%vb*cYgu*d%gmGW00000NkvXXu0mjfmz0(k 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 db6e667fb77d119f4150c6a1ec875be023846c19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 875 zcmV-x1C;!UP)~KgG zR}iEKYkd&s5>XHzgbo{2);@?U76v}7jm|c0SDM(WxlQlAUmue0ynNw%zURyLoI^kL zGTAy>AJ&1*viblBH+3K&3PcJG#WBDGup1&ZI*~%d$8{V4nu?GFD0|!@zybGp`T!?U z0Vu+FXuJxL@-`K&x$fN}fO2^v@PK`OBbv*7;lLJ~Gm(Khvu{j6SulUGk@WbdCub`` z#Z=lc0L-r_Os)Cmk-CR%xAg-4&)Qm_@CkdyHb!T9$TTis^htr($(@hgP-8ijR#wVq z%mpcm4H(sWsqG$YTgG7ffWpkif6KfeWw8MZbNlgLKhph95n^DFO6A(*_O}BS*bbHr zEidX={A}`M!Uli0op_nh zy7S_t!gp1q>P8?#z*R_CPxQ`+cX9UxkHv&I6 zXB;Pcc;Wu^DaQzc_;96iITfyx{hJFr)~AcC`;s&|vYODkUY~ObGd+?@ML(r5^Yv=ZHt8XtwHhg6(hr?2qSD`z z!Y34FRMNLOi`H#fFb6PcT4~E8X;k$v3Nx#@{{d={ku%1$CRzXh002ovPDHLkV1k;- BrV{`F 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 a6a37fe29c7f17cec2d4824961e1acfe37f0d353..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 823 zcmV-71IYY|P)de9(=SZW1EY1mdVOhDM_c6MG5vwyzZ`^%H}ee=A}$LQ95 zF=jyHg=34CF#l@Q#%R2kI}nFKWUgz1neW&VR+pM! zBwEhe<@Uy2y7&2nVtj@elmP1LJezOG3#aM=kNA}QW9}aFhB*PvWR_uB5A7lEkYhOWwzPxbk_-U#y zg(S~e8g@uwO?$n#9ByJQ zGvG#6dKXzeCxaQ8l3B+|Bi?fK&dC{e`mUQ!fzM=bPgsdwY<$iqS6e%2d0>LNqEgNQ z9~D6fzibBC`8{xD4m8{csK)>{_^E|vBliO?{{st?Q~gh3V Date: Tue, 27 Jan 2026 04:28:32 +0300 Subject: [PATCH 023/202] upd changelog and readme --- CHANGELOG.md | 5 ++-- ReadMe.md | 67 +++------------------------------------------------- 2 files changed, 6 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index edc777ae4..f87d730fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,6 @@ * SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) * SubGHz: **Jarolift protocol full support** (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) -* SubGHz: **Change key SEND action** (This changes solve problem when user press and release OK button too fast and signal was not fully sent) (PR #949 | by @Dmitry422) * SubGHz: **New modulation FSK with 12KHz deviation** * SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder * SubGHz: **Stilmatic - button 9 support** (two buttons hold simulation) (mapped on arrow keys) @@ -17,11 +16,13 @@ * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**26jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**27jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * UI: Various small changes +* Desktop: Disable winter holidays anims * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) * NFC: Fix LED not blinking at SLIX unlock (closes issue #945) +* SubGHz: Improve docs on low level code (PR #949 | by @Dmitry422) * SubGHz: Fix Alutech AT4N false positives * SubGHz: Cleanup of extra local variables * SubGHz: Replaced Cars ignore option with Revers RB2 protocol ignore option diff --git a/ReadMe.md b/ReadMe.md index 891fe9b8f..35f8759e6 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -141,75 +141,14 @@ Before getting started: Also check the [changelog in releases](https://github.com/DarkFlippers/unleashed-firmware/releases) for latest updates! ### Current modified and new Sub-GHz protocols list: -Thanks to Official team (to their SubGHz Developer, Skorp) for implementing support (decoder + encoder / or decode only) for these protocols in OFW. [Full list of supported protocols and their frequencies/modulations (to use in Read mode)](/documentation/SubGHzSupportedSystems.md) -> [!NOTE] -> Not all Keeloq systems are supported for decoding or emulation! ->
-> Supported Keeloq manufacturers include ->
-> -> | Column 1 | Column 2 | Column 3 | Column 4 | Column 5 | -> |-------------------|--------------|------------------|-------------------|------------------------| -> | Alligator | Comunello | GSN | Magic_4 | SL_A2-A4 | -> | Alligator_S-275 | Dea_Mio | Guard_RF-311A | Mongoose | SL_A6-A9/Tomahawk_9010 | -> | APS-1100_APS-2550 | DTM_Neo | Harpoon | Mutanco_Mutancode | SL_B6,B9_dop | -> | Aprimatic | DoorHan | IronLogic | NICE_MHOUSE | Sommer | -> | Beninca | EcoStar | JCM_Tech | NICE_Smilo | Stilmatic | -> | BFT | Elmes_Poland | KEY | Normstahl | Teco | -> | Came_Space | FAAC_RC,XT | Kingates_Stylo4k | Pantera | Tomahawk_TZ-9030 | -> | Cenmax | FAAC_SLH | KGB/Subaru | Pantera_CLK | Tomahawk_Z,X_3-5 | -> | Cenmax_St-5 | Faraon | Leopard | Pantera_XS/Jaguar | ZX-730-750-1055 | -> | Cenmax_St-7 | Genius_Bravo | Magic_1 | Partisan_RX | IL-100(Smart) | -> | Centurion | Gibidi | Magic_2 | Reff | Merlin | -> | Monarch | Jolly Motors | Magic_3 | Sheriff | Steelmate | -> | Motorline | Rosh | Pecinin | Rossi | Cardin S449 | ->
-
+Thanks to Official team (to their SubGHz Developer, Skorp) for implementing support (decoder + encoder / or decode only) for most protocols in OFW. -
-Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX -
+And thanks to our contributors who took part in SubGHz improvements and making of new features: -- Elplast/P-11B/3BK/E.C.A (static 18 bit) -- Roger (static 28 bit) with add manually support (thanks @mishamyte) -- V2 Phoenix (Phox) (dynamic 52 bit) (thanks @RocketGod-git) -- Marantec (static 49 bit) (add manually support and CRC verify) (thanks @li0ard) -- Feron (static 32 bit) -- ReversRB2 / RB2M (static 64 bit) with add manually support -- Marantec24 (static 24 bit) with add manually support -- GangQi (static 34 bit) with button parsing and add manually support (thanks to @mishamyte for captures and testing, thanks @Skorpionm for help) -- Hollarm (static 42 bit) with button parsing and add manually support (thanks to @mishamyte for captures, thanks @Skorpionm for help) -- Hay21 (dynamic 21 bit) with button parsing -- Nero Radio 57bit (+ 56bit support) -- CAME 12bit/24bit encoder fixes (Fixes are now merged in OFW) -- Keeloq: Dea Mio, Genius Bravo, GSN, HCS101, AN-Motors, JCM Tech, MHouse, Nice Smilo, DTM Neo, FAAC RC,XT, Mutancode, Normstahl, Beninca + Allmatic, Stilmatic, CAME Space, Aprimatic (model TR and similar), Centurion Nova (thanks Carlos !), Hormann EcoStar, Novoferm, Sommer, Monarch (thanks @ashphx !), Jolly Motors (thanks @pkooiman !), IL-100(Smart) (thx Vitaly for RAWs), Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate (thanks @RocketGod-git), Cardin S449 (thanks @zero-mega) -
- -
-Protocols support made by Skorp (original implementation) and @xMasterX (current version) -
- -- CAME Atomo → Update! check out new [instructions](/documentation/SubGHzRemoteProg.md) -- Nice Flor S → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) -- FAAC SLH (Spa) → Update!!! (Programming mode!) Check out new [instructions](/documentation/SubGHzRemoteProg.md) -- Keeloq: BFT Mitto → Update! Check out new [instructions](/documentation/SubGHzRemoteProg.md) -- Star Line -- Security+ v1 & v2 -
- -
-Encoders made by @assasinfil and @xMasterX -
- -- Somfy Telis → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) -- Somfy Keytis -- KingGates Stylo 4k (UPD: Add manually and all buttons support) -- Alutech AT-4N → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) -- Nice ON2E (Nice One) → How to create new remote - [instructions](/documentation/SubGHzRemoteProg.md) -
+`@RocketGod-git, @zero-mega, @ashphx, @pkooiman, Vitaly, Carlos, @li0ard, @mishamyte, d82k, Steffen (bastelbudenbuben de), @assasinfil, @gid9798` ## ❤️ Please support development of the project From 84f580d1684ad6ad1b830b5fb310bebd504badb1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 29 Jan 2026 03:17:41 +0300 Subject: [PATCH 024/202] subghz: fix stilmatic keeloq support, display decr. hop feature --- CHANGELOG.md | 3 +- documentation/SubGHzSupportedSystems.md | 2 +- lib/subghz/protocols/keeloq.c | 63 ++++++++++++++----------- 3 files changed, 38 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f87d730fd..4f37e21aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,13 +6,14 @@ * SubGHz: **Jarolift protocol full support** (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) * SubGHz: **New modulation FSK with 12KHz deviation** * SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder -* SubGHz: **Stilmatic - button 9 support** (two buttons hold simulation) (mapped on arrow keys) +* SubGHz: **Stilmatic (R-Tech) - 12bit discr. fix & button 9 support** (two buttons hold simulation) (mapped on arrow keys) * SubGHz: **Counter editor refactoring** (PR #939 | by @Dmitry422) * SubGHz: **Alutech AT-4N & Nice Flor S turbo speedup** (PR #942 | by @Dmitry422) * SubGHz: **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) * SubGHz: **Sommer - last button code 0x6 support** (mapped on arrow keys) * SubGHz: Add 390MHz, 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) * SubGHz: Fixed button mapping for **FAAC RC/XT** +* SubGHz: KeeLoq display decrypted hop in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 0cfcb8881..33e2d1cb2 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -124,7 +124,7 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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) ### Alarms, unknown origin, etc. - APS-1100/APS-2550 (KeeLoq, 64 bits) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index cd657f23f..837ae038e 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -95,7 +95,7 @@ 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); @@ -305,12 +305,14 @@ 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)) { // DTM Neo, Came_Space uses 12bit serial -> simple learning // FAAC_RC,XT , Mutanco_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 decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } else if( @@ -899,9 +901,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, @@ -924,7 +926,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; } @@ -938,7 +940,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: @@ -951,14 +953,14 @@ static uint8_t subghz_protocol_keeloq_check_remote_controller_selector( 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; @@ -976,7 +978,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; } else { if(reset_seed_back) instance->seed = 0; } @@ -988,7 +990,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: @@ -998,7 +1000,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: @@ -1008,7 +1010,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: @@ -1018,7 +1020,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_UNKNOWN: @@ -1028,7 +1030,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 @@ -1044,7 +1046,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; } //########################### @@ -1057,7 +1059,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 @@ -1067,7 +1069,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 @@ -1078,7 +1080,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 @@ -1089,7 +1091,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 @@ -1100,7 +1102,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 @@ -1110,7 +1112,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; @@ -1126,7 +1128,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) { @@ -1135,6 +1137,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(); @@ -1159,7 +1162,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 { @@ -1176,7 +1179,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); } } @@ -1213,6 +1216,8 @@ static void subghz_protocol_keeloq_check_remote_controller( } // Set max custom buttons subghz_custom_btn_set_max(4); + + return resdecrypt; } uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { @@ -1483,7 +1488,9 @@ 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; + + hopdecrypt = subghz_protocol_keeloq_check_remote_controller( &instance->generic, instance->keystore, &instance->manufacture_name); uint32_t code_found_hi = instance->generic.data >> 32; @@ -1511,7 +1518,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, instance->generic.seed); @@ -1549,7 +1556,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); } From c05ef82b11b93b35ff150b111859276782dafa2e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 29 Jan 2026 04:52:02 +0300 Subject: [PATCH 025/202] try to decode bft with zero seed ?! --- lib/subghz/protocols/keeloq.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 837ae038e..500ea6459 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -981,6 +981,16 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller_selector( 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: From 523a3723cf9ec475c2904603b422c55802120519 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:10:45 +0300 Subject: [PATCH 026/202] upd changelog and faq --- CHANGELOG.md | 3 ++- documentation/FAQ.md | 28 ++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f37e21aa..f8564a535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,8 @@ * SubGHz: **Sommer - last button code 0x6 support** (mapped on arrow keys) * SubGHz: Add 390MHz, 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) * SubGHz: Fixed button mapping for **FAAC RC/XT** -* SubGHz: KeeLoq display decrypted hop in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) +* SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) +* SubGHz: **BFT KeeLoq** try decoding with **zero seed** too * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) diff --git a/documentation/FAQ.md b/documentation/FAQ.md index cd38ad7a8..25f19e30f 100644 --- a/documentation/FAQ.md +++ b/documentation/FAQ.md @@ -44,21 +44,21 @@ Here - [link](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/docume ## I want to request or make new SubGHz protocol, my remote (is not car keyfob) and is not supported, how to record RAW signal properly? -1. Open SubGHz app, (if you know the frequency skip that step and go to Read) select Frequency analyzer, press and hold button on your remote and place it near IR window on flipper -You will find a approx. frequency that remote uses, release button on the remote and wait until frequency will be placed in history list -Hold OK on flipper to jump into Read mode, now try pressing your remote couple times holding it for at least 2 seconds -Try different modulations, AM650/FM238/FM476/FM12K - nothing works? Lets make RAW recording for analysis -2. Knowing the frequency open Read RAW and set it here in config page -Make sure RSSI Threshold is set to (----) -You need to make 1 RAW for each modulation AM650/FM238/FM476/FM12K -Press REC and on your remote press 1 button 5 times holding it for 1-2 seconds - then 5 times holding it for 5 seconds each time -If your remote has more than 1 button - record each button in similar way +1. Open SubGHz app, (if you know the frequency skip that step and go to Read) select Frequency analyzer, press and hold button on your remote and place it near IR window on flipper
+You will find a approx. frequency that remote uses, release button on the remote and wait until frequency will be placed in history list
+Hold OK on flipper to jump into Read mode, now try pressing your remote couple times holding it for at least 2 seconds
+Try different modulations, AM650/FM238/FM476/FM12K - nothing works? Lets make RAW recording for analysis
+2. Knowing the frequency open Read RAW and set it here in config page
+Make sure RSSI Threshold is set to (----)
+You need to make 1 RAW for each modulation AM650/FM238/FM476/FM12K
+Press REC and on your remote press 1 button 5 times holding it for 1-2 seconds - then 5 times holding it for 5 seconds each time
+If your remote has more than 1 button - record each button in similar way
Label each raw - what button you recorded -3. Copy all that RAW files to PC and create issue in firmware repo, attach raw's in archive -Provide high quality photos of the remote, if possible - photos of disassembled remote too -Its model, manufacturer, any known information -If you have access to receiver board, add a photo too -Done! If your remote appears not to be encrypted and very unique, it might be added soon +3. Copy all that RAW files to PC and create issue in firmware repo, attach raw's in archive
+Provide high quality photos of the remote, if possible - photos of disassembled remote too
+Its model, manufacturer, any known information
+If you have access to receiver board, add a photo too
+Done! If your remote appears not to be encrypted and very unique, it might be added soon
In case if you want to help us or analyze that signals youself there's a great online tool - https://lab.flipper.net/pulse-plotter ## How to build (compile) the firmware? From 2469206cc8b549f35d4c7fcbcd70d4986966ad60 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 29 Jan 2026 06:18:41 +0300 Subject: [PATCH 027/202] subghz fix and show prog mode bft --- CHANGELOG.md | 1 + lib/subghz/protocols/keeloq.c | 55 ++++++++++++++++++++++++----------- 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8564a535..da9bbee95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * SubGHz: Fixed button mapping for **FAAC RC/XT** * SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) * SubGHz: **BFT KeeLoq** try decoding with **zero seed** too +* SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 500ea6459..ca0db9f7b 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1515,23 +1515,44 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output 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; - 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); + 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) { instance->generic.cnt = 0x0; furi_string_cat_printf( From 1d734948d9592d3aa3339eb0e6bf633718510af3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 29 Jan 2026 07:36:42 +0300 Subject: [PATCH 028/202] fix about screen define --- applications/settings/about/about.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/settings/about/about.c b/applications/settings/about/about.c index d9fbca87a..efd53f210 100644 --- a/applications/settings/about/about.c +++ b/applications/settings/about/about.c @@ -156,9 +156,9 @@ static DialogMessageButton fw_version_screen(DialogsApp* dialogs, DialogMessage* buffer = furi_string_alloc(); const Version* ver = furi_hal_version_get_firmware_version(); const BleGlueC2Info* c2_ver = NULL; -#ifdef SRV_BT + //#ifdef SRV_BT c2_ver = ble_glue_get_c2_info(); -#endif + //#endif if(!ver) { //-V1051 furi_string_cat_printf(buffer, "No info\n"); From 0eae6030b0f173ec1e8edc06ce0f7abef06d0bcd Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Fri, 30 Jan 2026 21:43:05 +0700 Subject: [PATCH 029/202] work to home --- .../scenes/subghz_scene_signal_settings.c | 66 +++++++++++++++---- .../subghz/scenes/subghz_scene_transmitter.c | 61 ++++++++++++++++- lib/subghz/blocks/generic.h | 7 ++ lib/subghz/protocols/alutech_at_4n.c | 2 +- lib/subghz/protocols/ansonic.c | 4 +- 5 files changed, 124 insertions(+), 16 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index e74219a28..c8a0cf2a6 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -11,10 +11,15 @@ 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; +static uint8_t button = 0x0; +static uint8_t btn_byte_count = 1; +static uint8_t* btn_byte_ptr = NULL; + #define COUNTER_MODE_COUNT 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "System", @@ -79,8 +84,25 @@ 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) { + furi_string_cat_str(byte_input_text, " button number 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,9 +152,10 @@ 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; + bool button_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 @@ -143,6 +166,8 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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"); @@ -155,19 +180,31 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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; + 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); - byte_ptr = (uint8_t*)&counter16; - byte_count = 2; + cnt_byte_ptr = (uint8_t*)&counter16; + cnt_byte_count = 2; } } - furi_assert(byte_ptr); - furi_assert(byte_count > 0); + // ### 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; + } + + furi_assert(cnt_byte_ptr); + furi_assert(cnt_byte_count > 0); + furi_assert(btn_byte_ptr); //Create and Enable/Disable variable_item_list depending on current values VariableItemList* variable_item_list = subghz->variable_item_list; @@ -196,6 +233,11 @@ void subghz_scene_signal_settings_on_enter(void* context) { 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 !"); + 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_string_free(tmp_text); view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList); @@ -206,7 +248,7 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventByteInputDone) { - switch(byte_count) { + 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); diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index ebd69059f..b27d88da0 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -3,7 +3,14 @@ #include #include +<<<<<<< HEAD +======= +#include + +#include "applications/main/subghz/helpers/subghz_txrx_i.h" +#include "lib/subghz/blocks/generic.h" +>>>>>>> cf35909c8 (work to home) #define TAG "SubGhzSceneTransmitter" void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { @@ -67,7 +74,7 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventViewTransmitterSendStart) { subghz->state_notifications = SubGhzNotificationStateIDLE; - + FURI_LOG_D("000000", "PRESS"); if(subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx))) { subghz->state_notifications = SubGhzNotificationStateTx; subghz_scene_transmitter_update_data_show(subghz); @@ -75,8 +82,23 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { +<<<<<<< HEAD +======= + FURI_LOG_D("111111", "RELEASE"); + + // if we recieve event to stop tranmission (user release OK button) but + // hardware TX still working now then set flag to stop it after hardware TX will be realy ended + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + subghz_block_generic_global.endless_tx = true; + tx_stop_called = true; + FURI_LOG_D("111111", "STOP CALLED"); + return true; + } + // if hardware TX not working now so just stop TX correctly +>>>>>>> cf35909c8 (work to home) subghz->state_notifications = SubGhzNotificationStateIDLE; subghz_txrx_stop(subghz->txrx); + subghz_block_generic_global.endless_tx = false; 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(); @@ -90,6 +112,8 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { subghz_txrx_stop(subghz->txrx); furi_hal_subghz_set_rolling_counter_mult(tmp_counter); } + FURI_LOG_D("111111", "JUST STOP"); + return true; } else if(event.event == SubGhzCustomEventViewTransmitterBack) { subghz->state_notifications = SubGhzNotificationStateIDLE; @@ -102,7 +126,42 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeTick) { if(subghz->state_notifications == SubGhzNotificationStateTx) { +<<<<<<< HEAD notification_message(subghz->notifications, &sequence_blink_magenta_10); +======= + // if hardware TX still working at this time so we just blink led and do nothing + if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { + notification_message(subghz->notifications, &sequence_blink_magenta_10); + return true; + + // if hardware TX not working now and tx_stop_called = true + // (mean user release OK button early than hardware TX was ended) then we stop TX + if(tx_stop_called) { + FURI_LOG_D("22222222", "STOP BY CALL"); + + // tx_stop_called = false; + // subghz->state_notifications = SubGhzNotificationStateIDLE; + // subghz_txrx_stop(subghz->txrx); + // // subghz_block_generic_global.endless_tx = false; + // 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); + //} + return true; + } + subghz_block_generic_global.endless_tx = true; + FURI_LOG_D("22222222", "ENDELSS TX ON"); + } +>>>>>>> cf35909c8 (work to home) } return true; } diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 1e72c5a5e..daed68b07 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -35,6 +35,13 @@ struct SubGhzBlockGenericGlobal { 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) bool cnt_is_available; // is there counter available for protocol (used in counter editor giu) + + uint8_t current_btn; // global counter value; + uint8_t new_btn; // global counter value; + bool btn_need_override; // flag for protocols to override signals counter inside of protocols + bool btn_is_available; // is there counter available for protocol (used in counter editor giu) + + bool endless_tx; // used for endless/breakless transmission in subghz protols (when user not release OK button) }; extern SubGhzBlockGenericGlobal subghz_block_generic_global; //global structure for subghz diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 687a1e930..5ebe1eb43 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -135,7 +135,7 @@ LevelDuration subghz_protocol_encoder_alutech_at_4n_yield(void* context) { instance->encoder.repeat--; instance->encoder.front = 0; } - + FURI_LOG_D("ALLLLLLL", "REPEAT - %i ", instance->encoder.repeat); return ret; } diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 75f803370..1a9a69828 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -82,7 +82,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 = 30; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -183,7 +183,7 @@ LevelDuration subghz_protocol_encoder_ansonic_yield(void* context) { instance->encoder.repeat--; instance->encoder.front = 0; } - +FURI_LOG_D("ANNNN", "REPEAT - %i ",instance->encoder.repeat); return ret; } From 4800039d64b181e112eaf545295d226b00662584 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 31 Jan 2026 17:00:11 +0300 Subject: [PATCH 030/202] add info about stilmatic and cardin receivers --- documentation/SubGHzSupportedSystems.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 33e2d1cb2..faa42e9e1 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -98,7 +98,7 @@ 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) @@ -124,7 +124,7 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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 (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) +- Stilmatic (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (receiver checks for 10bit only (unverified)) ### Alarms, unknown origin, etc. - APS-1100/APS-2550 (KeeLoq, 64 bits) From 95dd537bf81ee445b1fb71aa60431cb62080333d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 1 Feb 2026 05:51:09 +0300 Subject: [PATCH 031/202] add v2 phox counter modes support --- CHANGELOG.md | 1 + .../scenes/subghz_scene_signal_settings.c | 1 + documentation/SubGHzCounterMode.md | 18 +++++ documentation/SubGHzSupportedSystems.md | 4 +- lib/subghz/protocols/phoenix_v2.c | 72 ++++++++++++++++--- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da9bbee95..fea108a20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * SubGHz: **Alutech AT-4N & Nice Flor S turbo speedup** (PR #942 | by @Dmitry422) * SubGHz: **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) * SubGHz: **Sommer - last button code 0x6 support** (mapped on arrow keys) +* SubGHz: **V2 Phoenix (Phox) added 2 counter modes support** (docs updated) * SubGHz: Add 390MHz, 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) * SubGHz: Fixed button mapping for **FAAC RC/XT** * SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index e74219a28..13c8234e2 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -47,6 +47,7 @@ static Protocols protocols[] = { {"CAME Atomo", 4}, {"Alutech AT-4N", 3}, {"KeeLoq", 7}, + {"Phoenix_V2", 3}, }; #define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols)); diff --git a/documentation/SubGHzCounterMode.md b/documentation/SubGHzCounterMode.md index 54fd0e24e..76eac58e4 100644 --- a/documentation/SubGHzCounterMode.md +++ b/documentation/SubGHzCounterMode.md @@ -129,6 +129,24 @@ CounterMode: 1 --- +### 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 ### Important Considerations: diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index faa42e9e1..eed5ee420 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -20,7 +20,7 @@ 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) +- Beninca ARC (TOGO2VA) `433.92MHz` `AM650` (128 bits, Dynamic AES) (button code `0` emulates `hidden button` option on the remote) - 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) @@ -46,7 +46,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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) diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 1f2731f54..e6d5d6710 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -91,6 +91,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); @@ -252,18 +254,30 @@ static bool btn = subghz_protocol_phoenix_v2_get_btn_code(); // 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(v2_phoenix_counter_mode == 0) { + // 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 += 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) { @@ -271,6 +285,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( @@ -326,6 +348,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)) { @@ -333,6 +367,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; @@ -582,10 +621,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) { From 3872c7a73d85ae0b7c0658c691098d0fea8e4ced Mon Sep 17 00:00:00 2001 From: Luis Mayo Valbuena Date: Sun, 1 Feb 2026 14:31:31 +0100 Subject: [PATCH 032/202] feat: add IR capabilities to the JS engine --- applications/system/js_app/application.fam | 8 ++ .../apps/Scripts/js_examples/infrared-send.js | 47 +++++++ applications/system/js_app/js_modules.c | 1 + .../js_app/modules/js_infrared/js_infrared.c | 129 ++++++++++++++++++ .../packages/fz-sdk/infrared/index.d.ts | 41 ++++++ 5 files changed, 226 insertions(+) create mode 100644 applications/system/js_app/examples/apps/Scripts/js_examples/infrared-send.js create mode 100644 applications/system/js_app/modules/js_infrared/js_infrared.c create mode 100644 applications/system/js_app/packages/fz-sdk/infrared/index.d.ts diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index abafb6ea0..baef951e8 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -240,6 +240,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/js_examples/infrared-send.js b/applications/system/js_app/examples/apps/Scripts/js_examples/infrared-send.js new file mode 100644 index 000000000..6606ef5f8 --- /dev/null +++ b/applications/system/js_app/examples/apps/Scripts/js_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 93c8b4af8..845a596ab 100644 --- a/applications/system/js_app/js_modules.c +++ b/applications/system/js_app/js_modules.c @@ -281,6 +281,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; From 1d32d1de5cd116dadb4f3f73a5c6d59c545afaad Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 2 Feb 2026 01:22:39 +0700 Subject: [PATCH 033/202] btn_is_available = true --- .../main/subghz/helpers/subghz_txrx.c | 5 - .../scenes/subghz_scene_receiver_info.c | 45 +++-- .../scenes/subghz_scene_signal_settings.c | 112 ++++++----- .../subghz/scenes/subghz_scene_transmitter.c | 95 +++------- lib/subghz/blocks/generic.c | 23 ++- lib/subghz/blocks/generic.h | 34 +++- lib/subghz/protocols/alutech_at_4n.c | 14 +- lib/subghz/protocols/ansonic.c | 13 +- lib/subghz/protocols/beninca_arc.c | 13 +- lib/subghz/protocols/bett.c | 4 +- lib/subghz/protocols/bin_raw.c | 4 +- lib/subghz/protocols/came.c | 4 +- lib/subghz/protocols/came_atomo.c | 13 +- lib/subghz/protocols/came_twee.c | 10 +- lib/subghz/protocols/chamberlain_code.c | 4 +- lib/subghz/protocols/clemsa.c | 13 +- lib/subghz/protocols/dickert_mahs.c | 4 +- lib/subghz/protocols/doitrand.c | 11 +- lib/subghz/protocols/dooya.c | 13 +- lib/subghz/protocols/elplast.c | 4 +- lib/subghz/protocols/faac_slh.c | 176 ++++++++++-------- lib/subghz/protocols/feron.c | 4 +- lib/subghz/protocols/gangqi.c | 14 +- lib/subghz/protocols/gate_tx.c | 11 +- lib/subghz/protocols/hay21.c | 13 +- lib/subghz/protocols/hollarm.c | 14 +- lib/subghz/protocols/holtek.c | 10 +- lib/subghz/protocols/holtek_ht12x.c | 10 +- lib/subghz/protocols/honeywell.c | 2 +- lib/subghz/protocols/honeywell_wdb.c | 4 +- lib/subghz/protocols/hormann.c | 12 +- lib/subghz/protocols/ido.c | 6 + lib/subghz/protocols/intertechno_v3.c | 14 +- lib/subghz/protocols/jarolift.c | 13 +- lib/subghz/protocols/keeloq.c | 17 ++ lib/subghz/protocols/kinggates_stylo_4k.c | 13 +- lib/subghz/protocols/legrand.c | 4 +- lib/subghz/protocols/linear.c | 4 +- lib/subghz/protocols/linear_delta3.c | 4 +- lib/subghz/protocols/magellan.c | 11 +- lib/subghz/protocols/marantec.c | 10 +- lib/subghz/protocols/marantec24.c | 10 +- lib/subghz/protocols/mastercode.c | 13 +- lib/subghz/protocols/megacode.c | 10 +- lib/subghz/protocols/nero_radio.c | 10 +- lib/subghz/protocols/nero_sketch.c | 4 +- lib/subghz/protocols/nice_flo.c | 4 +- lib/subghz/protocols/nice_flor_s.c | 13 +- lib/subghz/protocols/phoenix_v2.c | 13 +- lib/subghz/protocols/power_smart.c | 10 +- lib/subghz/protocols/princeton.c | 15 +- lib/subghz/protocols/revers_rb2.c | 4 +- lib/subghz/protocols/roger.c | 14 +- lib/subghz/protocols/secplus_v1.c | 9 +- lib/subghz/protocols/secplus_v2.c | 13 +- lib/subghz/protocols/smc5326.c | 4 +- lib/subghz/protocols/somfy_keytis.c | 16 +- lib/subghz/protocols/somfy_telis.c | 16 +- lib/subghz/protocols/treadmill37.c | 11 +- targets/f7/api_symbols.csv | 4 +- targets/f7/furi_hal/furi_hal_subghz.c | 10 +- 61 files changed, 675 insertions(+), 332 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 8abf373f4..ca3f0b300 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -234,7 +234,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"); @@ -244,10 +243,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; diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index e68b0203d..f2970d343 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) { @@ -133,25 +136,22 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) subghz_txrx_hopper_unpause(subghz->txrx); subghz->state_notifications = SubGhzNotificationStateRx; } else { + // 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; } return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { - //CC1101 Stop Tx -> Start RX + //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 = SubGhzNotificationStateIDLE; + 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_get_text_space_left(subghz->history, NULL)) { - subghz->state_notifications = SubGhzNotificationStateRx; - } - } return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoSave) { //CC1101 Stop RX -> Save @@ -188,6 +188,25 @@ 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 SubGhzNotificationStateIDLE: + // 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); + + 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_get_text_space_left(subghz->history, NULL)) { + subghz->state_notifications = SubGhzNotificationStateRx; + } + } + } + break; default: break; } diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index c8a0cf2a6..06fe5bc39 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -20,6 +20,8 @@ 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 7 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "System", @@ -72,6 +74,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"); @@ -90,7 +93,9 @@ void subghz_scene_signal_settings_variable_item_list_enter_callback(void* contex } // when we click OK on "Edit button" item if(index == 2) { - furi_string_cat_str(byte_input_text, " button number in HEX"); + 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; @@ -156,6 +161,28 @@ void subghz_scene_signal_settings_on_enter(void* context) { bool counter_not_available = true; bool button_not_available = true; + //Create and Enable/Disable variable_item_list depending on current values + VariableItemList* variable_item_list = subghz->variable_item_list; + int32_t value_index; + VariableItem* item; + + variable_item_list_set_enter_callback( + variable_item_list, + subghz_scene_signal_settings_variable_item_list_enter_callback, + subghz); + + item = variable_item_list_add( + variable_item_list, + "Counter Mode", + mode_count, + 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 @@ -191,6 +218,12 @@ void subghz_scene_signal_settings_on_enter(void* context) { } } + 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) { @@ -202,41 +235,15 @@ void subghz_scene_signal_settings_on_enter(void* context) { btn_byte_ptr = (uint8_t*)&button; } - furi_assert(cnt_byte_ptr); - furi_assert(cnt_byte_count > 0); - furi_assert(btn_byte_ptr); - - //Create and Enable/Disable variable_item_list depending on current values - VariableItemList* variable_item_list = subghz->variable_item_list; - int32_t value_index; - VariableItem* item; - - variable_item_list_set_enter_callback( - variable_item_list, - subghz_scene_signal_settings_variable_item_list_enter_callback, - subghz); - - item = variable_item_list_add( - variable_item_list, - "Counter Mode", - mode_count, - 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 !"); - - 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 !"); - 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); @@ -248,21 +255,40 @@ bool subghz_scene_signal_settings_on_event(void* context, SceneManagerEvent even if(event.type == SceneManagerEventTypeCustom) { if(event.event == SubGhzCustomEventByteInputDone) { - switch(cnt_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; } diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index b27d88da0..ed2f04389 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -3,14 +3,11 @@ #include #include -<<<<<<< HEAD -======= #include - #include "applications/main/subghz/helpers/subghz_txrx_i.h" #include "lib/subghz/blocks/generic.h" ->>>>>>> cf35909c8 (work to home) + #define TAG "SubGhzSceneTransmitter" void subghz_scene_transmitter_callback(SubGhzCustomEvent event, void* context) { @@ -70,11 +67,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; - FURI_LOG_D("000000", "PRESS"); + 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); @@ -82,41 +84,12 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } return true; } else if(event.event == SubGhzCustomEventViewTransmitterSendStop) { -<<<<<<< HEAD -======= - FURI_LOG_D("111111", "RELEASE"); - - // if we recieve event to stop tranmission (user release OK button) but - // hardware TX still working now then set flag to stop it after hardware TX will be realy ended - if(!subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) { - subghz_block_generic_global.endless_tx = true; - tx_stop_called = true; - FURI_LOG_D("111111", "STOP CALLED"); - return true; - } - // if hardware TX not working now so just stop TX correctly ->>>>>>> cf35909c8 (work to home) - subghz->state_notifications = SubGhzNotificationStateIDLE; - subghz_txrx_stop(subghz->txrx); + // 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; - 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); - } - FURI_LOG_D("111111", "JUST STOP"); - 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; @@ -126,42 +99,28 @@ bool subghz_scene_transmitter_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeTick) { if(subghz->state_notifications == SubGhzNotificationStateTx) { -<<<<<<< HEAD - notification_message(subghz->notifications, &sequence_blink_magenta_10); -======= - // if hardware TX still working at this time so we just blink led and do nothing + // 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 not working now and tx_stop_called = true - // (mean user release OK button early than hardware TX was ended) then we stop TX - if(tx_stop_called) { - FURI_LOG_D("22222222", "STOP BY CALL"); - - // tx_stop_called = false; - // subghz->state_notifications = SubGhzNotificationStateIDLE; - // subghz_txrx_stop(subghz->txrx); - // // subghz_block_generic_global.endless_tx = false; - // 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); - //} - 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); } - subghz_block_generic_global.endless_tx = true; - FURI_LOG_D("22222222", "ENDELSS TX ON"); } ->>>>>>> cf35909c8 (work to home) } return true; } diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index f5974ffaa..82fbc03c7 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 daed68b07..788467a79 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -30,35 +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; // global counter value; - uint8_t new_btn; // global counter value; - bool btn_need_override; // flag for protocols to override signals counter inside of protocols - bool btn_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 (when user not release OK button) + 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/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 5ebe1eb43..186d5fd25 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -94,7 +94,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; @@ -132,10 +132,10 @@ 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; } - FURI_LOG_D("ALLLLLLL", "REPEAT - %i ", instance->encoder.repeat); + return ret; } @@ -400,6 +400,10 @@ 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)) + 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; @@ -895,6 +899,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 1a9a69828..692e4aea5 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -82,7 +82,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 = 30; + instance->encoder.repeat = 3; instance->encoder.size_upload = 52; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -180,10 +180,10 @@ 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; } -FURI_LOG_D("ANNNN", "REPEAT - %i ",instance->encoder.repeat); + return ret; } @@ -323,6 +323,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 83db66306..308989d61 100644 --- a/lib/subghz/protocols/beninca_arc.c +++ b/lib/subghz/protocols/beninca_arc.c @@ -256,7 +256,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; @@ -350,6 +350,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( @@ -477,7 +481,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; } @@ -659,6 +663,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 44946a2f6..2d61e33a8 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -91,7 +91,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; @@ -199,7 +199,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 ca52cdd49..b3959363a 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -142,7 +142,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)); @@ -342,7 +342,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 2762a2484..04ea1767f 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -90,7 +90,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; @@ -211,7 +211,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 f8ec7baa0..f46b90b2d 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -89,7 +89,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; @@ -251,6 +251,10 @@ static void subghz_protocol_encoder_came_atomo_get_upload( btn = 0x6; } + // 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); @@ -430,7 +434,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; } @@ -829,6 +833,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 3bb909dc8..20a4c5cc3 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -109,7 +109,7 @@ 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.repeat = 3; instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -284,7 +284,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; } @@ -444,6 +444,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 fda224bb6..3e003d4b6 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -105,7 +105,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; @@ -254,7 +254,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 672abcba3..27c2e26c5 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -90,7 +90,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; @@ -199,7 +199,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; } @@ -338,10 +338,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 65be6fd0c..4d8ad3606 100644 --- a/lib/subghz/protocols/dickert_mahs.c +++ b/lib/subghz/protocols/dickert_mahs.c @@ -132,7 +132,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; @@ -237,7 +237,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/doitrand.c b/lib/subghz/protocols/doitrand.c index 7c7946042..1853bd16f 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -82,7 +82,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; @@ -179,7 +179,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; } @@ -333,6 +333,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 fd8645a0b..07dec5346 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -77,7 +77,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; @@ -189,7 +189,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; } @@ -418,16 +418,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 18d6d07b4..909689830 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -168,7 +168,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 147e452eb..fb82b1379 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -109,7 +109,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; @@ -124,108 +124,115 @@ void subghz_protocol_encoder_faac_slh_free(void* context) { } 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); - } + // 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(); + 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]; + // 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; + 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) { + 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(&temp_counter_backup)) { + 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((temp_counter_backup + furi_hal_subghz_get_rolling_counter_mult()) > + if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFF) { - temp_counter_backup = 0; + instance->generic.cnt = 0; } else { - temp_counter_backup += furi_hal_subghz_get_rolling_counter_mult(); + instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult(); } } } else { - // todo OFEX mode - temp_counter_backup += 1; + // 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[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] = (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]; + 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]; + 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; + 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); + 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; + } 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; + } } - // 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; + // 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; + } } } uint32_t fix = instance->generic.serial << 4 | instance->generic.btn; @@ -441,7 +448,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; } @@ -736,6 +743,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" @@ -756,6 +768,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 9591a4ebb..61dd5e445 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -74,7 +74,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; @@ -188,7 +188,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 6b5542410..f3e9c0daa 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -169,6 +169,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); @@ -295,7 +299,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; } @@ -461,6 +465,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 608567626..83a36be3a 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -75,7 +75,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; @@ -172,7 +172,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; } @@ -311,6 +311,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 ba6119dae..3495f5253 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -75,7 +75,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -144,6 +144,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) { @@ -309,7 +313,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; } @@ -468,6 +472,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 9b2a53a05..dc388b74a 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -168,6 +168,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) + @@ -296,7 +300,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; } @@ -468,6 +472,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 7ed5fc152..908d36a51 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -86,7 +86,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; @@ -185,7 +185,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; } @@ -344,6 +344,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 bf5e48adf..fa0ddb78b 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -94,7 +94,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; @@ -200,7 +200,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; } @@ -389,6 +389,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 1e3f231e8..30e3f4e9c 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -244,7 +244,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 0b8f63a24..fb421d099 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -88,7 +88,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; @@ -186,7 +186,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 f74a29fec..1c463ee89 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -80,7 +80,7 @@ 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.repeat = 3; instance->encoder.size_upload = 2048; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -110,7 +110,7 @@ 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 + instance->encoder.repeat = 3; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { //Send start bit @@ -185,7 +185,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; } @@ -319,6 +319,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 34e5c55a7..3e82b95fc 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -207,6 +207,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 71513051b..fe9ad7958 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -86,7 +86,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; @@ -207,7 +207,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; } @@ -444,6 +444,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" @@ -459,12 +463,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( @@ -472,5 +480,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 index 9881b9892..eae35bb31 100644 --- a/lib/subghz/protocols/jarolift.c +++ b/lib/subghz/protocols/jarolift.c @@ -100,7 +100,7 @@ void* subghz_protocol_encoder_jarolift_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; @@ -131,7 +131,7 @@ LevelDuration subghz_protocol_encoder_jarolift_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; } @@ -152,6 +152,10 @@ static bool 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 @@ -760,6 +764,11 @@ void subghz_protocol_decoder_jarolift_get_string(void* context, FuriString* outp 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" diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index ca0db9f7b..972229f68 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -135,6 +135,10 @@ static bool subghz_protocol_keeloq_gen_data( SubGhzProtocolEncoderKeeloq* instance, uint8_t btn, bool counter_up) { + // 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; @@ -1512,9 +1516,16 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; if(strcmp(instance->manufacture_name, "BFT") == 0) { + // 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; + // + ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_KEELOQ_BFT) { furi_string_cat_printf( @@ -1554,6 +1565,9 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output instance->generic.seed); } } else if(strcmp(instance->manufacture_name, "Unknown") == 0) { + 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; instance->generic.cnt = 0x0; furi_string_cat_printf( output, @@ -1574,6 +1588,9 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output 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" diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 9d7313559..29922c10e 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -101,7 +101,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; @@ -132,7 +132,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; } @@ -154,6 +154,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 @@ -727,6 +731,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 94a45694c..2a5078bde 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -81,7 +81,7 @@ 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.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; @@ -185,7 +185,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; } diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index f024316e9..f97b96e24 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -81,7 +81,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; @@ -190,7 +190,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 c2f527ba8..133f2b109 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -83,7 +83,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; @@ -194,7 +194,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 55048ca61..ed1b3b95b 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -78,7 +78,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; @@ -196,7 +196,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; } @@ -499,6 +499,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 53e4d6895..a07a559fc 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -77,7 +77,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; @@ -243,7 +243,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; } @@ -393,6 +393,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/marantec24.c b/lib/subghz/protocols/marantec24.c index 6f636e8ab..b03fa2095 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -183,7 +183,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; } @@ -332,6 +332,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 e4fae40b3..1dc924c7a 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -89,7 +89,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; @@ -199,7 +199,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; } @@ -343,10 +343,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 2c4bf10a3..cf7737aaa 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -87,7 +87,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; @@ -218,7 +218,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; } @@ -405,6 +405,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 da5497feb..212dc684f 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -77,7 +77,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; @@ -210,7 +210,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; } @@ -430,6 +430,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 64a75cfc0..a8a011aa6 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -76,7 +76,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; @@ -191,7 +191,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 2e5fa96b5..f7c8fe757 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -75,7 +75,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; @@ -177,7 +177,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; } diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 9085ee431..e1d796ae3 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -104,7 +104,7 @@ 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.repeat = 1; instance->encoder.size_upload = 2400; //wrong!! upload 186*16 = 2976 - actual size about 1728 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -147,6 +147,10 @@ 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)) + 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."); @@ -349,7 +353,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; } @@ -937,6 +941,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/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 1f2731f54..f3cd07ed1 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -77,7 +77,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; @@ -251,6 +251,10 @@ 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)) + 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) { @@ -364,7 +368,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; } @@ -598,6 +602,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 6449f720a..78915d2b6 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -85,7 +85,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; @@ -236,7 +236,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; } @@ -369,6 +369,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 cce8a7a34..f77e0e722 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -93,7 +93,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; @@ -257,6 +257,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) { @@ -410,7 +414,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; } @@ -590,8 +594,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" @@ -610,6 +620,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/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 941ff5c56..af063051b 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -78,7 +78,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -204,7 +204,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 9c33b11ec..8eccee566 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -76,7 +76,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -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) { @@ -303,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; } @@ -433,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 13af0d302..9b2d77913 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -98,7 +98,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; @@ -344,7 +344,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; } @@ -586,6 +586,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 ad343968b..cab5d575f 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -92,7 +92,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; @@ -395,6 +395,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}; @@ -623,7 +627,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; } @@ -966,6 +970,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 217dbb780..7e4fd2698 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -101,7 +101,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; @@ -208,7 +208,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 c9f6f47bd..38873d064 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -81,7 +81,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; @@ -130,6 +130,10 @@ static bool instance->generic.cnt = (data >> 24) & 0xFFFF; instance->generic.serial = data & 0xFFFFFF; + // 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); + // 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 @@ -455,7 +459,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; } @@ -801,13 +805,18 @@ 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, @@ -816,5 +825,6 @@ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* instance->press_duration_counter, 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 2be378b7d..c0b26c945 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -82,7 +82,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; @@ -124,6 +124,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 @@ -384,7 +388,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; } @@ -758,13 +762,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, @@ -772,5 +781,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 e9915c296..fdd5b3d71 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -73,7 +73,7 @@ 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.repeat = 3; instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; @@ -179,7 +179,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; } @@ -333,6 +333,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/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1ad9bc413..831932efe 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.4,, +Version,+,87.5,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -3613,6 +3613,8 @@ Function,-,strxfrm_l,size_t,"char*, const char*, size_t, locale_t" 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* diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index ece8dde2c..05dbfdc6f 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -677,10 +677,12 @@ 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); @@ -771,7 +773,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* // 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.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 = @@ -838,7 +840,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); + dma_config.PeriphOrM2MSrcAddress = (uint32_t)&(gpio->port->BSRR); dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_CIRCULAR; dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; From a9972e6af218dcfdfb8c915eb56029452274eea4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:47:20 +0300 Subject: [PATCH 034/202] add fix for phox --- lib/subghz/protocols/phoenix_v2.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 56a65bc8c..2dc199795 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -118,9 +118,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); From 6d70d0f27f43bfc99f34e4c035c643646ff36123 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:48:53 +0300 Subject: [PATCH 035/202] fmt --- targets/f7/furi_hal/furi_hal_subghz.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/targets/f7/furi_hal/furi_hal_subghz.c b/targets/f7/furi_hal/furi_hal_subghz.c index 05dbfdc6f..b23b2ea75 100644 --- a/targets/f7/furi_hal/furi_hal_subghz.c +++ b/targets/f7/furi_hal/furi_hal_subghz.c @@ -678,7 +678,7 @@ 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 +// 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 @@ -773,7 +773,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* // 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.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 = @@ -840,7 +840,7 @@ bool furi_hal_subghz_start_async_tx(FuriHalSubGhzAsyncTxCallback callback, void* furi_hal_subghz_debug_gpio_buff[1] = (uint32_t)gpio->pin << GPIO_NUMBER; dma_config.MemoryOrM2MDstAddress = (uint32_t)furi_hal_subghz_debug_gpio_buff; - dma_config.PeriphOrM2MSrcAddress = (uint32_t)&(gpio->port->BSRR); + dma_config.PeriphOrM2MSrcAddress = (uint32_t) & (gpio->port->BSRR); dma_config.Direction = LL_DMA_DIRECTION_MEMORY_TO_PERIPH; dma_config.Mode = LL_DMA_MODE_CIRCULAR; dma_config.PeriphOrM2MSrcIncMode = LL_DMA_PERIPH_NOINCREMENT; From 8676e8a7b0d799b8034a625b8d7cf204d1e17968 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Tue, 3 Feb 2026 01:33:13 +0700 Subject: [PATCH 036/202] prevent from extra ticking when key pressed with rgb_mod_installed --- .../services/notification/notification_app.c | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 68573c201..5f8f5c8db 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -33,8 +33,12 @@ static uint8_t notification_settings_get_display_brightness(NotificationApp* app static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value); static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app); -// --- RGB BACKLIGHT --- +// status of lcd backlight +// used to ignore backlight_on event if backlight active now +// prevent from extra ticking when key pressed with rgb_mod_installed +static bool lcd_backligth_is_on = false; +// --- RGB BACKLIGHT --- // local variable for local use uint8_t rgb_backlight_installed_variable = 0; @@ -330,6 +334,11 @@ static void notification_apply_notification_led_layer( layer->index = LayerNotification; // set layer layer->value[LayerNotification] = layer_value; + + // if layer.light = LightBacklight and backlight active now then just exit. + // prevent from extra ticking when key pressed with rgb_mod_installed + if((layer->light == LightBacklight) & lcd_backligth_is_on) return; + // apply furi_hal_light_set(layer->light, layer->value[LayerNotification]); } @@ -371,11 +380,9 @@ static void notification_reset_notification_layer( } if(reset_mask & reset_display_mask) { if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { - // --- NIGHT SHIFT --- furi_hal_light_set( LightBacklight, app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); - // --- NIGHT SHIFT END--- } if(app->settings.display_off_delay_ms > 0) { furi_timer_start( @@ -460,26 +467,31 @@ static void notification_process_notification_message( while(notification_message != NULL) { switch(notification_message->type) { case NotificationMessageTypeLedDisplayBacklight: - // if on - switch on and start timer - // if off - switch off and stop timer - // on timer - switch off - // --- NIGHT SHIFT --- + // if on (data.led.value =0xFF) - switch on and start timer + // if off (data.led.value =0x0) - switch off and stop timer if(notification_message->data.led.value > 0x00) { + // Backlight ON notification_apply_notification_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); + reset_mask |= reset_display_mask; + lcd_backligth_is_on = true; //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too rainbow_timer_starter(app); - // --- NIGHT SHIFT END --- + } else { + // Backlight OFF reset_mask &= ~reset_display_mask; notification_reset_notification_led_layer(&app->display); + lcd_backligth_is_on = false; + if(furi_timer_is_running(app->display_timer)) { furi_timer_stop(app->display_timer); } + //stop rgb_mod_rainbow_timer when display backlight is OFF if(furi_timer_is_running(app->rainbow_timer)) { rainbow_timer_stop(app); @@ -487,13 +499,13 @@ static void notification_process_notification_message( } break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: - // --- NIGHT SHIFT --- if(!app->display_led_lock) { app->display_led_lock = true; notification_apply_internal_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); + lcd_backligth_is_on = true; } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: @@ -505,7 +517,7 @@ static void notification_process_notification_message( app->current_night_shift * 1.0f); // --- NIGHT SHIFT END --- } else { - FURI_LOG_E(TAG, "Incorrect BacklightEnforce use"); + FURI_LOG_E(TAG, "Incorrect BacklightEnforceAuto usage"); } break; case NotificationMessageTypeLedRed: From da0cb9f908ef0be363146b309e1d04fae7f8bc77 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:29:03 +0300 Subject: [PATCH 037/202] Update documentation link for HT12A protocol by carlogrisetti --- lib/subghz/protocols/holtek_ht12x.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index fa0ddb78b..25dfdb5ca 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 * */ From 4ab0c3cb164cefa57bb41e986a2d1084a06dcb70 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:37:07 +0300 Subject: [PATCH 038/202] upd changelog --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fea108a20..4f0a4cbf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Main changes -- Current API: 87.4 +- Current API: 87.5 * SubGHz: **Cardin S449 protocol full support** (64bit keeloq) (with Add manually, and all button codes) (**use FSK12K modulation to read the remote**) (closes issues #735 #908) (by @xMasterX and @zero-mega (thanks!)) * SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) @@ -17,15 +17,18 @@ * SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) * SubGHz: **BFT KeeLoq** try decoding with **zero seed** too * SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) +* SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 | by @Dmitry422) +* JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**27jan2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**3feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * UI: Various small changes * Desktop: Disable winter holidays anims * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) * NFC: Fix LED not blinking at SLIX unlock (closes issue #945) +* SubGHz: Fix documentation link for HT12A protocol (by @carlogrisetti) * SubGHz: Improve docs on low level code (PR #949 | by @Dmitry422) * SubGHz: Fix Alutech AT4N false positives * SubGHz: Cleanup of extra local variables From 1088e342e230f1b0428482a56ea73bead4b96185 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:35:27 +0300 Subject: [PATCH 039/202] fix typo [ci skip] --- applications/services/notification/notification_app.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 5f8f5c8db..97a7401a8 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -36,7 +36,7 @@ static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* a // status of lcd backlight // used to ignore backlight_on event if backlight active now // prevent from extra ticking when key pressed with rgb_mod_installed -static bool lcd_backligth_is_on = false; +static bool lcd_backlight_is_on = false; // --- RGB BACKLIGHT --- // local variable for local use @@ -337,7 +337,7 @@ static void notification_apply_notification_led_layer( // if layer.light = LightBacklight and backlight active now then just exit. // prevent from extra ticking when key pressed with rgb_mod_installed - if((layer->light == LightBacklight) & lcd_backligth_is_on) return; + if((layer->light == LightBacklight) & lcd_backlight_is_on) return; // apply furi_hal_light_set(layer->light, layer->value[LayerNotification]); @@ -477,7 +477,7 @@ static void notification_process_notification_message( app->current_night_shift * 1.0f); reset_mask |= reset_display_mask; - lcd_backligth_is_on = true; + lcd_backlight_is_on = true; //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too rainbow_timer_starter(app); @@ -486,7 +486,7 @@ static void notification_process_notification_message( // Backlight OFF reset_mask &= ~reset_display_mask; notification_reset_notification_led_layer(&app->display); - lcd_backligth_is_on = false; + lcd_backlight_is_on = false; if(furi_timer_is_running(app->display_timer)) { furi_timer_stop(app->display_timer); @@ -505,7 +505,7 @@ static void notification_process_notification_message( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); - lcd_backligth_is_on = true; + lcd_backlight_is_on = true; } break; case NotificationMessageTypeLedDisplayBacklightEnforceAuto: From 67e191b135960726b1ecb194272bc5e0a816263b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:06:02 +0300 Subject: [PATCH 040/202] Keeloq ultimate, and some fixes and improvements [ci skip] - fix repeat values - fix endless tx missing - add mode 7 aka counter bypass - take some ram - free some ram - fix comments --- .../scenes/subghz_scene_signal_settings.c | 6 +- lib/subghz/protocols/came_twee.c | 2 +- lib/subghz/protocols/elplast.c | 2 +- lib/subghz/protocols/hay21.c | 2 +- lib/subghz/protocols/hollarm.c | 2 +- lib/subghz/protocols/hormann.c | 6 +- lib/subghz/protocols/keeloq.c | 214 +++++++++++------- lib/subghz/protocols/nice_flor_s.c | 2 +- lib/subghz/protocols/roger.c | 2 +- lib/subghz/protocols/treadmill37.c | 2 +- 10 files changed, 144 insertions(+), 96 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index ae52e7314..90f99e50c 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -22,7 +22,7 @@ static uint8_t* btn_byte_ptr = NULL; static uint8_t submenu_called = 0; -#define COUNTER_MODE_COUNT 7 +#define COUNTER_MODE_COUNT 8 static const char* const counter_mode_text[COUNTER_MODE_COUNT] = { "System", "Mode 1", @@ -31,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] = { @@ -41,6 +42,7 @@ static const int32_t counter_mode_value[COUNTER_MODE_COUNT] = { 4, 5, 6, + 7, }; typedef struct { @@ -53,7 +55,7 @@ static Protocols protocols[] = { {"Nice FloR-S", 3}, {"CAME Atomo", 4}, {"Alutech AT-4N", 3}, - {"KeeLoq", 7}, + {"KeeLoq", 8}, {"Phoenix_V2", 3}, }; diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 20a4c5cc3..d6b656a57 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -110,7 +110,7 @@ void* subghz_protocol_encoder_came_twee_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 1536; //max upload 92*14 = 1288 !!!! + instance->encoder.size_upload = 1536; // 1308 instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; diff --git a/lib/subghz/protocols/elplast.c b/lib/subghz/protocols/elplast.c index 909689830..ee029bfc7 100644 --- a/lib/subghz/protocols/elplast.c +++ b/lib/subghz/protocols/elplast.c @@ -74,7 +74,7 @@ void* subghz_protocol_encoder_elplast_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 256; + instance->encoder.size_upload = 64; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; diff --git a/lib/subghz/protocols/hay21.c b/lib/subghz/protocols/hay21.c index 3495f5253..19995932c 100644 --- a/lib/subghz/protocols/hay21.c +++ b/lib/subghz/protocols/hay21.c @@ -76,7 +76,7 @@ void* subghz_protocol_encoder_hay21_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 256; + instance->encoder.size_upload = 64; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index dc388b74a..fade91278 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -77,7 +77,7 @@ void* subghz_protocol_encoder_hollarm_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 256; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index 1c463ee89..f8a2a9ed5 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -80,8 +80,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 = 3; - 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; @@ -110,7 +110,6 @@ static bool subghz_protocol_encoder_hormann_get_upload(SubGhzProtocolEncoderHorm } else { instance->encoder.size_upload = size_upload; } - instance->encoder.repeat = 3; //original remote does 10 repeats for(size_t repeat = 0; repeat < 20; repeat++) { //Send start bit @@ -137,6 +136,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; } diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 972229f68..d2182a396 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -100,6 +100,14 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller( 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)); @@ -107,8 +115,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; @@ -134,7 +142,57 @@ 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)) { + 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); @@ -150,7 +208,6 @@ static bool subghz_protocol_keeloq_gen_data( } // 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) { @@ -259,8 +316,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) { @@ -437,7 +502,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); @@ -463,7 +528,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); @@ -471,88 +536,22 @@ 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; - } - - uint32_t gap_duration = subghz_protocol_keeloq_const.te_short * 40; - if((strcmp(instance->manufacture_name, "Sommer") == 0)) { - 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)) { + if(subghz_protocol_keeloq_gen_data(instance, btn, counter_up, false)) { // 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; + uint32_t gap_duration = subghz_protocol_keeloq_const.te_short * 40; + if((strcmp(instance->manufacture_name, "Sommer") == 0)) { + gap_duration = subghz_protocol_keeloq_const.te_short * 29; } //Send header @@ -593,6 +592,52 @@ 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(keeloq_counter_mode == 7) { + 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; } @@ -719,7 +764,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; } @@ -1366,7 +1411,8 @@ static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code) { // Set custom button // Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | - if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) { + if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0) && + (keeloq_counter_mode != 7)) { // Restore original button code btn = original_btn_code; } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) { diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index e1d796ae3..b554deccf 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -105,7 +105,7 @@ void* subghz_protocol_encoder_nice_flor_s_alloc(SubGhzEnvironment* environment) TAG, "Loading rainbow table from %s", instance->nice_flor_s_rainbow_table_file_name); } instance->encoder.repeat = 1; - instance->encoder.size_upload = 2400; //wrong!! upload 186*16 = 2976 - actual size about 1728 + 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; diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 8eccee566..35d80788d 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -77,7 +77,7 @@ void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 256; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; diff --git a/lib/subghz/protocols/treadmill37.c b/lib/subghz/protocols/treadmill37.c index fdd5b3d71..ddb8ba021 100644 --- a/lib/subghz/protocols/treadmill37.c +++ b/lib/subghz/protocols/treadmill37.c @@ -74,7 +74,7 @@ void* subghz_protocol_encoder_treadmill37_alloc(SubGhzEnvironment* environment) instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 256; + instance->encoder.size_upload = 128; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; From 97eaee54c8f12c0103487c7c5c8df6ba0b538e12 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:48:58 +0300 Subject: [PATCH 041/202] upd changelog and docs --- CHANGELOG.md | 2 ++ documentation/SubGHzCounterMode.md | 6 ++++++ lib/subghz/protocols/keeloq.c | 13 +++---------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0a4cbf0..aed0abbae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) * SubGHz: **BFT KeeLoq** try decoding with **zero seed** too * SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) +* SubGHz: KeeLoq **add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps)** - may bypass counter on some receivers! * SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 | by @Dmitry422) * JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) @@ -28,6 +29,7 @@ * Desktop: Disable winter holidays anims * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) * NFC: Fix LED not blinking at SLIX unlock (closes issue #945) +* SubGHz: Added some RAM * SubGHz: Fix documentation link for HT12A protocol (by @carlogrisetti) * SubGHz: Improve docs on low level code (PR #949 | by @Dmitry422) * SubGHz: Fix Alutech AT4N false positives diff --git a/documentation/SubGHzCounterMode.md b/documentation/SubGHzCounterMode.md index 76eac58e4..47fc8cf1f 100644 --- a/documentation/SubGHzCounterMode.md +++ b/documentation/SubGHzCounterMode.md @@ -127,6 +127,12 @@ 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) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index d2182a396..f47f53492 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -202,10 +202,6 @@ static bool subghz_protocol_keeloq_gen_data( 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 if(strcmp(instance->manufacture_name, "BFT") == 0) { @@ -543,10 +539,8 @@ static size_t subghz_protocol_encoder_keeloq_encode_to_timings( size_t index) { furi_assert(instance); // Generate new key - if(subghz_protocol_keeloq_gen_data(instance, btn, counter_up, false)) { - // OK - } else { - return false; + if(!subghz_protocol_keeloq_gen_data(instance, btn, counter_up, false)) { + return 0; } uint32_t gap_duration = subghz_protocol_keeloq_const.te_short * 40; @@ -1411,8 +1405,7 @@ static uint8_t subghz_protocol_keeloq_get_btn_code(uint8_t last_btn_code) { // Set custom button // Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0xA or Special Learning Code | - if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0) && - (keeloq_counter_mode != 7)) { + 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) { From 80158e84a21aabb42f4e3926718636333b7871cd Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 5 Feb 2026 18:04:42 +0700 Subject: [PATCH 042/202] subgh rpc refactor to "one click send signal" --- .../main/subghz/scenes/subghz_scene_rpc.c | 102 +++++++++++------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index b262679a4..bb6b29fc7 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -1,6 +1,9 @@ #include "../subghz_i.h" #include +#include +//#include +#include "applications/main/subghz/helpers/subghz_txrx_i.h" typedef enum { SubGhzRpcStateIdle, @@ -52,9 +55,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 +69,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 +88,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 +150,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; } From 162f9d3da33bd4be35b8e38b2edf3082997d6d3f Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 5 Feb 2026 18:16:31 +0700 Subject: [PATCH 043/202] clean --- applications/main/subghz/scenes/subghz_scene_rpc.c | 1 - 1 file changed, 1 deletion(-) diff --git a/applications/main/subghz/scenes/subghz_scene_rpc.c b/applications/main/subghz/scenes/subghz_scene_rpc.c index bb6b29fc7..f3753a3bc 100644 --- a/applications/main/subghz/scenes/subghz_scene_rpc.c +++ b/applications/main/subghz/scenes/subghz_scene_rpc.c @@ -2,7 +2,6 @@ #include #include -//#include #include "applications/main/subghz/helpers/subghz_txrx_i.h" typedef enum { From 30d48c112b86bf16e9f79419426d8ee91d427733 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:18:11 +0300 Subject: [PATCH 044/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aed0abbae..c1272d88d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ * SubGHz: **BFT KeeLoq** try decoding with **zero seed** too * SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) * SubGHz: KeeLoq **add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps)** - may bypass counter on some receivers! -* SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 | by @Dmitry422) +* SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 #958 | by @Dmitry422) * JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) From a515f435fc31d121848526d040ea955e9824f05f Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:11:03 +1100 Subject: [PATCH 045/202] UNLEASHED:: TX Power setting to SubGhz App * Works in Read, and Read RAW. * You can now adjust the TX power for testing devices without desyncing them from inside * Lets you do RTL testing etc on very low power. CODE REFACTORED, subghz_txrx_set_tx_power added. --- .../main/subghz/helpers/subghz_txrx.c | 50 ++++++++++-- .../main/subghz/helpers/subghz_txrx.h | 18 ++++- .../subghz/scenes/subghz_scene_read_raw.c | 3 +- .../subghz/scenes/subghz_scene_receiver.c | 6 +- .../scenes/subghz_scene_receiver_config.c | 78 +++++++++++++++++-- .../scenes/subghz_scene_receiver_info.c | 4 + applications/main/subghz/subghz.c | 8 +- applications/main/subghz/subghz_i.c | 11 ++- applications/main/subghz/subghz_i.h | 2 +- .../main/subghz/subghz_last_settings.c | 9 +++ .../main/subghz/subghz_last_settings.h | 1 + 11 files changed, 167 insertions(+), 23 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index ca3f0b300..46d0a7eef 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -101,12 +101,39 @@ 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->data = preset_data; preset->data_size = preset_data_size; } +uint8_t* + subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint32_t tx_power) { +#define TX_POWER_OFFSET 7 +#define TX_PRESET_POWER_COUNT 11 + const uint32_t tx_power_value[TX_PRESET_POWER_COUNT] = { + 0, + 0xC0, + 0xC5, + 0xCD, + 0x86, + 0x50, + 0x37, + 0x26, + 0x1D, + 0x17, + 0x03, + }; + + //Set the TX Power Here in the CC1101 register... + if(tx_power) + preset_data[preset_data_size - TX_POWER_OFFSET] = (uint8_t)tx_power_value[tx_power]; + + //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 = ""; @@ -677,20 +704,27 @@ void subghz_txrx_set_default_preset(SubGhzTxRx* instance, uint32_t frequency) { subghz_txrx_set_preset(instance, default_modulation, frequency, 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, + uint32_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); - subghz_txrx_set_preset( - instance, - preset_name, - frequency, - subghz_setting_get_preset_data(setting, index), - subghz_setting_get_preset_data_size(setting, index)); + //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, 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 0f3b64304..259fddfc2 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -57,6 +57,16 @@ 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, uint32_t tx_power); + /** * Get name of preset * @@ -360,7 +370,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, + uint32_t tx_power); diff --git a/applications/main/subghz/scenes/subghz_scene_read_raw.c b/applications/main/subghz/scenes/subghz_scene_read_raw.c index 355c339ff..6fe093dfc 100644 --- a/applications/main/subghz/scenes/subghz_scene_read_raw.c +++ b/applications/main/subghz/scenes/subghz_scene_read_raw.c @@ -110,7 +110,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 1a0063418..b525a2847 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver.c @@ -165,11 +165,15 @@ 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->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 a6e3324be..6842ca727 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -7,6 +7,7 @@ enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, + SubGhzSettingIndexTXPower, SubGhzSettingIndexBinRAW, SubGhzSettingIndexIgnoreReversRB2, SubGhzSettingIndexIgnoreAlarms, @@ -102,6 +103,22 @@ const char* const combobox_text[COMBO_BOX_COUNT] = { "ON", }; +//TX Power +#define TX_POWER_COUNT 11 +const char* const tx_power_text[TX_POWER_COUNT] = { + "Preset", + "12dBm", + "10dBm", + "7dBm", + "5dBm", + "0dBm", + "-6dBm", + "-10dBm", + "-15dBm", + "-20dBm", + "-30dBm", +}; + static void subghz_scene_receiver_config_set_ignore_filter(VariableItem* item, SubGhzProtocolFlag filter) { SubGhz* subghz = variable_item_get_context(item); @@ -185,6 +202,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), @@ -211,12 +233,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, - subghz_setting_get_preset_data(setting, index), - subghz_setting_get_preset_data_size(setting, index)); + subghz->txrx, preset_name, preset.frequency, preset_data, preset_data_size); subghz->last_settings->preset_index = index; } @@ -242,6 +266,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, @@ -273,6 +300,30 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[index]); } +static void subghz_scene_receiver_config_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->tx_power = index; + subghz->last_settings->tx_power = subghz->tx_power; + + //Get current preset and frequency so I can update preset wit TX power. + SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); + uint32_t frequency = subghz_setting_get_default_frequency(setting); + SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); + + //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, furi_string_get_cstr(preset.name), frequency, preset.data, preset.data_size); +} + static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -341,7 +392,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, + 0); SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); @@ -359,7 +411,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, subghz->last_settings->ignore_filter = subghz->ignore_filter; subghz->last_settings->filter = subghz->filter; subghz->last_settings->delete_old_signals = false; - + subghz->last_settings->tx_power = subghz->tx_power = 0; subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]); subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]); @@ -429,6 +481,18 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text(item, hopping_mode_text[value_index]); } + //Add TX Power + item = variable_item_list_add( + subghz->variable_item_list, + "TX Power", + TX_POWER_COUNT, + subghz_scene_receiver_config_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]); + if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerSet) { item = variable_item_list_add( diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index f2970d343..61502c16a 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -36,6 +36,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), diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index f8877551d..51b7316e4 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -207,7 +207,10 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { if(!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(); } @@ -218,10 +221,13 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { if(!alloc_for_tx_only) { 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->tx_power = 0; } + subghz_txrx_receiver_set_filter(subghz->txrx, subghz->filter); subghz_txrx_set_need_save_callback(subghz->txrx, subghz_save_to_file, subghz); diff --git a/applications/main/subghz/subghz_i.c b/applications/main/subghz/subghz_i.c index 1bdaf07d9..078fa1f2c 100644 --- a/applications/main/subghz/subghz_i.c +++ b/applications/main/subghz/subghz_i.c @@ -137,12 +137,19 @@ 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, - 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 10a2ce9f8..01f3ccf2f 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -93,7 +93,7 @@ struct SubGhz { uint16_t idx_menu_chosen; SubGhzLoadTypeFile load_type_file; - + uint32_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 0a4783a23..a38f39026 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -19,6 +19,7 @@ #define SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD "DelOldSignals" #define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold" #define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp" +#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower" SubGhzLastSettings* subghz_last_settings_alloc(void) { SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings)); @@ -119,6 +120,10 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count 1)) { flipper_format_rewind(fff_data_file); } + if(!flipper_format_read_uint32( + fff_data_file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &instance->tx_power, 1)) { + flipper_format_rewind(fff_data_file); + } if(!flipper_format_read_float( fff_data_file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD, @@ -222,6 +227,10 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { break; } + if(!flipper_format_write_uint32( + file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &instance->tx_power, 1)) { + break; + } if(!flipper_format_write_float( file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD, diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index 78de5e51c..e42ee868d 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -26,6 +26,7 @@ typedef struct { bool delete_old_signals; float hopping_threshold; bool leds_and_amp; + uint32_t tx_power; } SubGhzLastSettings; SubGhzLastSettings* subghz_last_settings_alloc(void); From e0c1a89c5dc766cef270bdc2ca0a62396331f8e7 Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:00:19 +1100 Subject: [PATCH 046/202] Refactor: Replace 2 assignments with one-liner --- applications/main/subghz/scenes/subghz_scene_receiver_config.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index 6842ca727..c743e660c 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -308,8 +308,7 @@ static void subghz_scene_receiver_config_set_tx_power(VariableItem* item) { variable_item_set_current_value_text(item, tx_power_text[index]); //Set TX power and remember setting - subghz->tx_power = index; - subghz->last_settings->tx_power = subghz->tx_power; + subghz->last_settings->tx_power = subghz->tx_power = index; //Get current preset and frequency so I can update preset wit TX power. SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); From 8f248a49da5e26e9c413a387a19f33df1596d99b Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 8 Feb 2026 10:16:15 +0700 Subject: [PATCH 047/202] Backlight settings bug --- applications/services/notification/notification.h | 1 + .../services/notification/notification_app.c | 13 +++++++++++++ .../services/notification/notification_messages.c | 12 ++++++++++++ .../services/notification/notification_messages.h | 3 ++- .../notification_settings_app.c | 7 +++---- targets/f7/api_symbols.csv | 3 ++- 6 files changed, 33 insertions(+), 6 deletions(-) diff --git a/applications/services/notification/notification.h b/applications/services/notification/notification.h index 0e1c07e5d..54e1eb895 100644 --- a/applications/services/notification/notification.h +++ b/applications/services/notification/notification.h @@ -65,6 +65,7 @@ typedef enum { NotificationMessageTypeDelay, NotificationMessageTypeLedDisplayBacklight, + NotificationMessageTypeLedDisplayBacklightForceOn, NotificationMessageTypeLedDisplayBacklightEnforceOn, NotificationMessageTypeLedDisplayBacklightEnforceAuto, diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 97a7401a8..b4b0effa3 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -498,6 +498,19 @@ static void notification_process_notification_message( } } break; + case NotificationMessageTypeLedDisplayBacklightForceOn: + // Force Backlight ON even if its ON now + lcd_backlight_is_on = false; + notification_apply_notification_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + reset_mask |= reset_display_mask; + lcd_backlight_is_on = true; + + //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too + rainbow_timer_starter(app); + break; case NotificationMessageTypeLedDisplayBacklightEnforceOn: if(!app->display_led_lock) { app->display_led_lock = true; diff --git a/applications/services/notification/notification_messages.c b/applications/services/notification/notification_messages.c index 3dc154654..e3f4fbc2c 100644 --- a/applications/services/notification/notification_messages.c +++ b/applications/services/notification/notification_messages.c @@ -17,6 +17,12 @@ const NotificationMessage message_display_backlight_off = { .data.led.value = 0x00, }; +/** Display: backlight wakeup even if its ON now */ +const NotificationMessage message_display_backlight_force_on = { + .type = NotificationMessageTypeLedDisplayBacklightForceOn, + .data.led.value = 0xFF, +}; + /** Display: backlight always on */ const NotificationMessage message_display_backlight_enforce_on = { .type = NotificationMessageTypeLedDisplayBacklightEnforceOn, @@ -259,6 +265,12 @@ const NotificationSequence sequence_display_backlight_off = { NULL, }; +/** Display: backlight wakeup even if its ON now */ +const NotificationSequence sequence_display_backlight_force_on = { + &message_display_backlight_force_on, + NULL, +}; + /** Display: backlight always on lock */ const NotificationSequence sequence_display_backlight_enforce_on = { &message_display_backlight_enforce_on, diff --git a/applications/services/notification/notification_messages.h b/applications/services/notification/notification_messages.h index 3960d93b7..8af499841 100644 --- a/applications/services/notification/notification_messages.h +++ b/applications/services/notification/notification_messages.h @@ -87,7 +87,8 @@ extern const NotificationSequence sequence_display_backlight_on; extern const NotificationSequence sequence_display_backlight_off; /** Display: backlight force off after a delay of 1000ms */ extern const NotificationSequence sequence_display_backlight_off_delay_1000; - +/** Display: backlight wakeup even if its ON now */ +extern const NotificationSequence sequence_display_backlight_force_on; /** Display: backlight always on lock */ extern const NotificationSequence sequence_display_backlight_enforce_on; /** Display: backlight always on unlock */ diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 533db3cd0..4d95a8ac6 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -295,7 +295,7 @@ static void backlight_changed(VariableItem* item) { variable_item_set_current_value_text(item, backlight_text[index]); app->notification->settings.display_brightness = backlight_value[index]; - notification_message(app->notification, &sequence_display_backlight_on); + notification_message(app->notification, &sequence_display_backlight_force_on); } static void screen_changed(VariableItem* item) { @@ -557,11 +557,10 @@ static void night_shift_changed(VariableItem* item) { variable_item_set_current_value_text(item, night_shift_text[index]); app->notification->settings.night_shift = night_shift_value[index]; - app->notification->current_night_shift = night_shift_value[index]; - app->notification->current_night_shift = night_shift_value[index]; // force demo night_shift brightness to rgb backlight and stock backlight - notification_message(app->notification, &sequence_display_backlight_on); + // app->notification->current_night_shift = night_shift_value[index]; + // notification_message(app->notification, &sequence_display_backlight_force_on); for(int i = 4; i < 6; i++) { VariableItem* t_item = variable_item_list_get(app->variable_item_list, i); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 831932efe..e3159a17e 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.5,, +Version,+,87.6,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -5383,6 +5383,7 @@ Variable,+,sequence_charged,const NotificationSequence, Variable,+,sequence_charging,const NotificationSequence, Variable,+,sequence_display_backlight_enforce_auto,const NotificationSequence, Variable,+,sequence_display_backlight_enforce_on,const NotificationSequence, +Variable,+,sequence_display_backlight_force_on,const NotificationSequence, Variable,+,sequence_display_backlight_off,const NotificationSequence, Variable,+,sequence_display_backlight_off_delay_1000,const NotificationSequence, Variable,+,sequence_display_backlight_on,const NotificationSequence, From 168240c2378c76e7b29b3a0c5513d985e69ad545 Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Sun, 8 Feb 2026 14:17:04 +1100 Subject: [PATCH 048/202] TX POWER: Move from Receiver Config to Radio Settings --- .../scenes/subghz_scene_radio_settings.c | 42 +++++++++++++++ .../scenes/subghz_scene_receiver_config.c | 54 +------------------ 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 8e48d0c91..87e2d8269 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -66,6 +66,22 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { -50, }; +//TX Power +#define TX_POWER_COUNT 11 +const char* const tx_power_text[TX_POWER_COUNT] = { + "Preset", + "12dBm", + "10dBm", + "7dBm", + "5dBm", + "0dBm", + "-6dBm", + "-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); @@ -80,6 +96,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); @@ -144,6 +174,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, "Protocol Names", diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_config.c b/applications/main/subghz/scenes/subghz_scene_receiver_config.c index c743e660c..4781b326e 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_config.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_config.c @@ -7,7 +7,6 @@ enum SubGhzSettingIndex { SubGhzSettingIndexFrequency, SubGhzSettingIndexHopping, SubGhzSettingIndexModulation, - SubGhzSettingIndexTXPower, SubGhzSettingIndexBinRAW, SubGhzSettingIndexIgnoreReversRB2, SubGhzSettingIndexIgnoreAlarms, @@ -103,22 +102,6 @@ const char* const combobox_text[COMBO_BOX_COUNT] = { "ON", }; -//TX Power -#define TX_POWER_COUNT 11 -const char* const tx_power_text[TX_POWER_COUNT] = { - "Preset", - "12dBm", - "10dBm", - "7dBm", - "5dBm", - "0dBm", - "-6dBm", - "-10dBm", - "-15dBm", - "-20dBm", - "-30dBm", -}; - static void subghz_scene_receiver_config_set_ignore_filter(VariableItem* item, SubGhzProtocolFlag filter) { SubGhz* subghz = variable_item_get_context(item); @@ -300,29 +283,6 @@ static void subghz_scene_receiver_config_set_speaker(VariableItem* item) { subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[index]); } -static void subghz_scene_receiver_config_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; - - //Get current preset and frequency so I can update preset wit TX power. - SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); - uint32_t frequency = subghz_setting_get_default_frequency(setting); - SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); - - //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, furi_string_get_cstr(preset.name), frequency, preset.data, preset.data_size); -} - static void subghz_scene_receiver_config_set_bin_raw(VariableItem* item) { SubGhz* subghz = variable_item_get_context(item); uint8_t index = variable_item_get_current_value_index(item); @@ -392,7 +352,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context, subghz->txrx, SUBGHZ_LAST_SETTING_DEFAULT_FREQUENCY, SUBGHZ_LAST_SETTING_DEFAULT_PRESET, - 0); + subghz->tx_power); SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx); SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx); @@ -480,18 +440,6 @@ void subghz_scene_receiver_config_on_enter(void* context) { variable_item_set_current_value_text(item, hopping_mode_text[value_index]); } - //Add TX Power - item = variable_item_list_add( - subghz->variable_item_list, - "TX Power", - TX_POWER_COUNT, - subghz_scene_receiver_config_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]); - if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) != SubGhzCustomEventManagerSet) { item = variable_item_list_add( From 8af7467b02c2aa120c693552f2b54b098b18e6b2 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 8 Feb 2026 17:57:56 +0700 Subject: [PATCH 049/202] Rework night shift setting demo --- .../services/notification/notification_app.c | 1275 +++++++++-------- .../services/notification/notification_app.h | 1 + .../notification_settings_app.c | 16 +- 3 files changed, 657 insertions(+), 635 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index b4b0effa3..d3a0e65bc 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -248,7 +248,7 @@ void night_shift_timer_start(NotificationApp* app) { if(furi_timer_is_running(app->night_shift_timer)) { furi_timer_stop(app->night_shift_timer); } - furi_timer_start(app->night_shift_timer, furi_ms_to_ticks(2000)); + furi_timer_start(app->night_shift_timer, furi_ms_to_ticks(1000)); } } @@ -277,710 +277,723 @@ void night_shift_timer_callback(void* context) { } } -// --- NIGHT SHIFT END --- - -void notification_message_save_settings(NotificationApp* app) { - NotificationAppMessage m = { - .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; - furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); - furi_event_flag_wait( - m.back_event, NOTIFICATION_EVENT_COMPLETE, FuriFlagWaitAny, FuriWaitForever); - furi_event_flag_free(m.back_event); +// force backlight ON when night_shift_demo_timer will be ended +void night_shift_demo_timer_callback(void* context) { + furi_assert(context); + NotificationApp* app = context; + notification_message(app, &sequence_display_backlight_force_on); } + // --- NIGHT SHIFT END --- -// internal layer -static void - notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); + void notification_message_save_settings(NotificationApp * app) { + NotificationAppMessage m = { + .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; + furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); + furi_event_flag_wait( + m.back_event, NOTIFICATION_EVENT_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_event_flag_free(m.back_event); + } - // set value - layer->value[LayerInternal] = layer_value; + // internal layer + static void notification_apply_internal_led_layer( + NotificationLedLayer * layer, uint8_t layer_value) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); - // apply if current layer is internal - if(layer->index == LayerInternal) { + // set value + layer->value[LayerInternal] = layer_value; + + // apply if current layer is internal + if(layer->index == LayerInternal) { + furi_hal_light_set(layer->light, layer->value[LayerInternal]); + } + } + + static void notification_apply_lcd_contrast(NotificationApp * app) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); + furi_record_close(RECORD_GUI); + } + + static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp * app) { + bool result = false; + if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || + (app->led[2].index == LayerInternal)) { + if((app->led[0].value[LayerInternal] != 0x00) || + (app->led[1].value[LayerInternal] != 0x00) || + (app->led[2].value[LayerInternal] != 0x00)) { + result = true; + } + } + + return result; + } + + // notification layer + static void notification_apply_notification_led_layer( + NotificationLedLayer * layer, const uint8_t layer_value) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); + + // set value + layer->index = LayerNotification; + // set layer + layer->value[LayerNotification] = layer_value; + + // if layer.light = LightBacklight and backlight active now then just exit. + // prevent from extra ticking when key pressed with rgb_mod_installed + if((layer->light == LightBacklight) & lcd_backlight_is_on) return; + + // apply + furi_hal_light_set(layer->light, layer->value[LayerNotification]); + } + + static void notification_reset_notification_led_layer(NotificationLedLayer * layer) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); + + // set value + layer->value[LayerNotification] = 0; + // set layer + layer->index = LayerInternal; + + // apply furi_hal_light_set(layer->light, layer->value[LayerInternal]); } -} -static void notification_apply_lcd_contrast(NotificationApp* app) { - Gui* gui = furi_record_open(RECORD_GUI); - u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); - furi_record_close(RECORD_GUI); -} - -static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) { - bool result = false; - if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || - (app->led[2].index == LayerInternal)) { - if((app->led[0].value[LayerInternal] != 0x00) || - (app->led[1].value[LayerInternal] != 0x00) || - (app->led[2].value[LayerInternal] != 0x00)) { - result = true; + static void notification_reset_notification_layer( + NotificationApp * app, uint8_t reset_mask, float display_brightness_set) { + if(reset_mask & reset_blink_mask) { + furi_hal_light_blink_stop(); + } + if(reset_mask & reset_red_mask) { + notification_reset_notification_led_layer(&app->led[0]); + } + if(reset_mask & reset_green_mask) { + notification_reset_notification_led_layer(&app->led[1]); + } + if(reset_mask & reset_blue_mask) { + notification_reset_notification_led_layer(&app->led[2]); + } + if(reset_mask & reset_vibro_mask) { + notification_vibro_off(); + } + if(reset_mask & reset_sound_mask) { + notification_sound_off(); + } + if(reset_mask & reset_display_mask) { + if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { + furi_hal_light_set( + LightBacklight, + app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); + } + if(app->settings.display_off_delay_ms > 0) { + furi_timer_start( + app->display_timer, notification_settings_display_off_delay_ticks(app)); + } } } - return result; -} - -// notification layer -static void notification_apply_notification_led_layer( - NotificationLedLayer* layer, - const uint8_t layer_value) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); - - // set value - layer->index = LayerNotification; - // set layer - layer->value[LayerNotification] = layer_value; - - // if layer.light = LightBacklight and backlight active now then just exit. - // prevent from extra ticking when key pressed with rgb_mod_installed - if((layer->light == LightBacklight) & lcd_backlight_is_on) return; - - // apply - furi_hal_light_set(layer->light, layer->value[LayerNotification]); -} - -static void notification_reset_notification_led_layer(NotificationLedLayer* layer) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); - - // set value - layer->value[LayerNotification] = 0; - // set layer - layer->index = LayerInternal; - - // apply - furi_hal_light_set(layer->light, layer->value[LayerInternal]); -} - -static void notification_reset_notification_layer( - NotificationApp* app, - uint8_t reset_mask, - float display_brightness_set) { - if(reset_mask & reset_blink_mask) { - furi_hal_light_blink_stop(); - } - if(reset_mask & reset_red_mask) { - notification_reset_notification_led_layer(&app->led[0]); - } - if(reset_mask & reset_green_mask) { - notification_reset_notification_led_layer(&app->led[1]); - } - if(reset_mask & reset_blue_mask) { - notification_reset_notification_led_layer(&app->led[2]); - } - if(reset_mask & reset_vibro_mask) { - notification_vibro_off(); - } - if(reset_mask & reset_sound_mask) { - notification_sound_off(); - } - if(reset_mask & reset_display_mask) { - if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { - furi_hal_light_set( - LightBacklight, - app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); - } - if(app->settings.display_off_delay_ms > 0) { - furi_timer_start( - app->display_timer, notification_settings_display_off_delay_ticks(app)); + static void notification_apply_notification_leds( + NotificationApp * app, const uint8_t* values) { + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + notification_apply_notification_led_layer( + &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); } } -} -static void notification_apply_notification_leds(NotificationApp* app, const uint8_t* values) { - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - notification_apply_notification_led_layer( - &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); + // settings + uint8_t notification_settings_get_display_brightness(NotificationApp * app, uint8_t value) { + return value * app->settings.display_brightness; } -} -// settings -uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value) { - return value * app->settings.display_brightness; -} - -static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) { - return value * app->settings.led_brightness; -} - -static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { - return (float)(app->settings.display_off_delay_ms) / - (1000.0f / furi_kernel_get_tick_frequency()); -} - -// generics -static void notification_vibro_on(bool force) { - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { - furi_hal_vibro_on(true); + static uint8_t notification_settings_get_rgb_led_brightness( + NotificationApp * app, uint8_t value) { + return value * app->settings.led_brightness; } -} -static void notification_vibro_off(void) { - furi_hal_vibro_on(false); -} + static uint32_t notification_settings_display_off_delay_ticks(NotificationApp * app) { + return (float)(app->settings.display_off_delay_ms) / + (1000.0f / furi_kernel_get_tick_frequency()); + } -static void notification_sound_on(float freq, float volume, bool force) { - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { - if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { - furi_hal_speaker_start(freq, volume); + // generics + static void notification_vibro_on(bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + furi_hal_vibro_on(true); } } -} -static void notification_sound_off(void) { - if(furi_hal_speaker_is_mine()) { - furi_hal_speaker_stop(); - furi_hal_speaker_release(); + static void notification_vibro_off(void) { + furi_hal_vibro_on(false); } -} -// display timer -static void notification_display_timer(void* ctx) { - furi_assert(ctx); - NotificationApp* app = ctx; - notification_message(app, &sequence_display_backlight_off); -} + static void notification_sound_on(float freq, float volume, bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(freq, volume); + } + } + } -// message processing -static void notification_process_notification_message( - NotificationApp* app, - NotificationAppMessage* message) { - uint32_t notification_message_index = 0; - bool force_volume = false; - bool force_vibro = false; - const NotificationMessage* notification_message; - notification_message = (*message->sequence)[notification_message_index]; + static void notification_sound_off(void) { + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); + } + } - bool led_active = false; - uint8_t led_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; - bool reset_notifications = true; - float speaker_volume_setting = app->settings.speaker_volume; - bool vibro_setting = app->settings.vibro_on; - float display_brightness_setting = app->settings.display_brightness; + // display timer + static void notification_display_timer(void* ctx) { + furi_assert(ctx); + NotificationApp* app = ctx; + notification_message(app, &sequence_display_backlight_off); + } - uint8_t reset_mask = 0; + // message processing + static void notification_process_notification_message( + NotificationApp * app, NotificationAppMessage * message) { + uint32_t notification_message_index = 0; + bool force_volume = false; + bool force_vibro = false; + const NotificationMessage* notification_message; + notification_message = (*message->sequence)[notification_message_index]; - while(notification_message != NULL) { - switch(notification_message->type) { - case NotificationMessageTypeLedDisplayBacklight: - // if on (data.led.value =0xFF) - switch on and start timer - // if off (data.led.value =0x0) - switch off and stop timer - if(notification_message->data.led.value > 0x00) { - // Backlight ON + bool led_active = false; + uint8_t led_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; + bool reset_notifications = true; + float speaker_volume_setting = app->settings.speaker_volume; + bool vibro_setting = app->settings.vibro_on; + float display_brightness_setting = app->settings.display_brightness; + + uint8_t reset_mask = 0; + + while(notification_message != NULL) { + switch(notification_message->type) { + case NotificationMessageTypeLedDisplayBacklight: + // if on (data.led.value =0xFF) - switch on and start timer + // if off (data.led.value =0x0) - switch off and stop timer + if(notification_message->data.led.value > 0x00) { + // Backlight ON + notification_apply_notification_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + + reset_mask |= reset_display_mask; + lcd_backlight_is_on = true; + + //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too + rainbow_timer_starter(app); + + } else { + // Backlight OFF + reset_mask &= ~reset_display_mask; + notification_reset_notification_led_layer(&app->display); + lcd_backlight_is_on = false; + + if(furi_timer_is_running(app->display_timer)) { + furi_timer_stop(app->display_timer); + } + + //stop rgb_mod_rainbow_timer when display backlight is OFF + if(furi_timer_is_running(app->rainbow_timer)) { + rainbow_timer_stop(app); + } + } + break; + case NotificationMessageTypeLedDisplayBacklightForceOn: + // Force Backlight ON even if its ON now + lcd_backlight_is_on = false; notification_apply_notification_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); - reset_mask |= reset_display_mask; lcd_backlight_is_on = true; //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too rainbow_timer_starter(app); - - } else { - // Backlight OFF - reset_mask &= ~reset_display_mask; - notification_reset_notification_led_layer(&app->display); - lcd_backlight_is_on = false; - - if(furi_timer_is_running(app->display_timer)) { - furi_timer_stop(app->display_timer); + break; + case NotificationMessageTypeLedDisplayBacklightEnforceOn: + if(!app->display_led_lock) { + app->display_led_lock = true; + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + lcd_backlight_is_on = true; } - - //stop rgb_mod_rainbow_timer when display backlight is OFF - if(furi_timer_is_running(app->rainbow_timer)) { - rainbow_timer_stop(app); + break; + case NotificationMessageTypeLedDisplayBacklightEnforceAuto: + if(app->display_led_lock) { + app->display_led_lock = false; + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + // --- NIGHT SHIFT END --- + } else { + FURI_LOG_E(TAG, "Incorrect BacklightEnforceAuto usage"); } - } - break; - case NotificationMessageTypeLedDisplayBacklightForceOn: - // Force Backlight ON even if its ON now - lcd_backlight_is_on = false; - notification_apply_notification_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - reset_mask |= reset_display_mask; - lcd_backlight_is_on = true; - - //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too - rainbow_timer_starter(app); - break; - case NotificationMessageTypeLedDisplayBacklightEnforceOn: - if(!app->display_led_lock) { - app->display_led_lock = true; - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - lcd_backlight_is_on = true; - } - break; - case NotificationMessageTypeLedDisplayBacklightEnforceAuto: - if(app->display_led_lock) { - app->display_led_lock = false; - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - // --- NIGHT SHIFT END --- - } else { - FURI_LOG_E(TAG, "Incorrect BacklightEnforceAuto usage"); - } - break; - case NotificationMessageTypeLedRed: - // store and send on delay or after seq - led_active = true; - led_values[0] = notification_message->data.led.value; - app->led[0].value_last[LayerNotification] = led_values[0]; - reset_mask |= reset_red_mask; - break; - case NotificationMessageTypeLedGreen: - // store and send on delay or after seq - led_active = true; - led_values[1] = notification_message->data.led.value; - app->led[1].value_last[LayerNotification] = led_values[1]; - reset_mask |= reset_green_mask; - break; - case NotificationMessageTypeLedBlue: - // store and send on delay or after seq - led_active = true; - led_values[2] = notification_message->data.led.value; - app->led[2].value_last[LayerNotification] = led_values[2]; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLedBlinkStart: - // store and send on delay or after seq - led_active = true; - furi_hal_light_blink_start( - notification_message->data.led_blink.color, - app->settings.led_brightness * 255, - notification_message->data.led_blink.on_time, - notification_message->data.led_blink.period); - reset_mask |= reset_blink_mask; - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLedBlinkColor: - led_active = true; - furi_hal_light_blink_set_color(notification_message->data.led_blink.color); - break; - case NotificationMessageTypeLedBlinkStop: - furi_hal_light_blink_stop(); - reset_mask &= ~reset_blink_mask; - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeVibro: - if(notification_message->data.vibro.on) { - if(vibro_setting) notification_vibro_on(force_vibro); - } else { - notification_vibro_off(); - } - reset_mask |= reset_vibro_mask; - break; - case NotificationMessageTypeSoundOn: - notification_sound_on( - notification_message->data.sound.frequency, - notification_message->data.sound.volume * speaker_volume_setting, - force_volume); - reset_mask |= reset_sound_mask; - break; - case NotificationMessageTypeSoundOff: - notification_sound_off(); - reset_mask |= reset_sound_mask; - break; - case NotificationMessageTypeDelay: - if(led_active) { - if(notification_is_any_led_layer_internal_and_not_empty(app)) { - notification_apply_notification_leds(app, led_off_values); - furi_delay_ms(minimal_delay); - } - - led_active = false; - - notification_apply_notification_leds(app, led_values); + break; + case NotificationMessageTypeLedRed: + // store and send on delay or after seq + led_active = true; + led_values[0] = notification_message->data.led.value; + app->led[0].value_last[LayerNotification] = led_values[0]; + reset_mask |= reset_red_mask; + break; + case NotificationMessageTypeLedGreen: + // store and send on delay or after seq + led_active = true; + led_values[1] = notification_message->data.led.value; + app->led[1].value_last[LayerNotification] = led_values[1]; + reset_mask |= reset_green_mask; + break; + case NotificationMessageTypeLedBlue: + // store and send on delay or after seq + led_active = true; + led_values[2] = notification_message->data.led.value; + app->led[2].value_last[LayerNotification] = led_values[2]; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeLedBlinkStart: + // store and send on delay or after seq + led_active = true; + furi_hal_light_blink_start( + notification_message->data.led_blink.color, + app->settings.led_brightness * 255, + notification_message->data.led_blink.on_time, + notification_message->data.led_blink.period); + reset_mask |= reset_blink_mask; reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeLedBlinkColor: + led_active = true; + furi_hal_light_blink_set_color(notification_message->data.led_blink.color); + break; + case NotificationMessageTypeLedBlinkStop: + furi_hal_light_blink_stop(); + reset_mask &= ~reset_blink_mask; + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeVibro: + if(notification_message->data.vibro.on) { + if(vibro_setting) notification_vibro_on(force_vibro); + } else { + notification_vibro_off(); + } + reset_mask |= reset_vibro_mask; + break; + case NotificationMessageTypeSoundOn: + notification_sound_on( + notification_message->data.sound.frequency, + notification_message->data.sound.volume * speaker_volume_setting, + force_volume); + reset_mask |= reset_sound_mask; + break; + case NotificationMessageTypeSoundOff: + notification_sound_off(); + reset_mask |= reset_sound_mask; + break; + case NotificationMessageTypeDelay: + if(led_active) { + if(notification_is_any_led_layer_internal_and_not_empty(app)) { + notification_apply_notification_leds(app, led_off_values); + furi_delay_ms(minimal_delay); + } + + led_active = false; + + notification_apply_notification_leds(app, led_values); + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + } + + furi_delay_ms(notification_message->data.delay.length); + break; + case NotificationMessageTypeDoNotReset: + reset_notifications = false; + break; + case NotificationMessageTypeForceSpeakerVolumeSetting: + speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; + force_volume = true; + break; + case NotificationMessageTypeForceVibroSetting: + vibro_setting = notification_message->data.forced_settings.vibro; + force_vibro = true; + break; + case NotificationMessageTypeForceDisplayBrightnessSetting: + display_brightness_setting = + notification_message->data.forced_settings.display_brightness; + break; + case NotificationMessageTypeLedBrightnessSettingApply: + led_active = true; + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + led_values[i] = app->led[i].value_last[LayerNotification]; + } + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeLcdContrastUpdate: + notification_apply_lcd_contrast(app); + break; + } + notification_message_index++; + notification_message = (*message->sequence)[notification_message_index]; + }; + + // send and do minimal delay + if(led_active) { + bool need_minimal_delay = false; + if(notification_is_any_led_layer_internal_and_not_empty(app)) { + need_minimal_delay = true; } - furi_delay_ms(notification_message->data.delay.length); - break; - case NotificationMessageTypeDoNotReset: - reset_notifications = false; - break; - case NotificationMessageTypeForceSpeakerVolumeSetting: - speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; - force_volume = true; - break; - case NotificationMessageTypeForceVibroSetting: - vibro_setting = notification_message->data.forced_settings.vibro; - force_vibro = true; - break; - case NotificationMessageTypeForceDisplayBrightnessSetting: - display_brightness_setting = - notification_message->data.forced_settings.display_brightness; - break; - case NotificationMessageTypeLedBrightnessSettingApply: - led_active = true; - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - led_values[i] = app->led[i].value_last[LayerNotification]; - } + notification_apply_notification_leds(app, led_values); reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLcdContrastUpdate: - notification_apply_lcd_contrast(app); - break; - } - notification_message_index++; - notification_message = (*message->sequence)[notification_message_index]; - }; - // send and do minimal delay - if(led_active) { - bool need_minimal_delay = false; - if(notification_is_any_led_layer_internal_and_not_empty(app)) { - need_minimal_delay = true; - } - - notification_apply_notification_leds(app, led_values); - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - - if((need_minimal_delay) && (reset_notifications)) { - notification_apply_notification_leds(app, led_off_values); - furi_delay_ms(minimal_delay); - } - } - - if(reset_notifications) { - notification_reset_notification_layer(app, reset_mask, display_brightness_setting); - } -} - -static void - notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { - uint32_t notification_message_index = 0; - const NotificationMessage* notification_message; - notification_message = (*message->sequence)[notification_message_index]; - - while(notification_message != NULL) { - switch(notification_message->type) { - case NotificationMessageTypeLedDisplayBacklight: - notification_apply_internal_led_layer( - &app->display, - notification_settings_get_display_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedRed: - app->led[0].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[0], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedGreen: - app->led[1].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[1], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedBlue: - app->led[2].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[2], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedBrightnessSettingApply: - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - uint8_t new_val = notification_settings_get_rgb_led_brightness( - app, app->led[i].value_last[LayerInternal]); - notification_apply_internal_led_layer(&app->led[i], new_val); + if((need_minimal_delay) && (reset_notifications)) { + notification_apply_notification_leds(app, led_off_values); + furi_delay_ms(minimal_delay); } - break; - default: - break; } - notification_message_index++; + + if(reset_notifications) { + notification_reset_notification_layer(app, reset_mask, display_brightness_setting); + } + } + + static void notification_process_internal_message( + NotificationApp * app, NotificationAppMessage * message) { + uint32_t notification_message_index = 0; + const NotificationMessage* notification_message; notification_message = (*message->sequence)[notification_message_index]; - } -} -static bool notification_load_settings(NotificationApp* app) { - NotificationSettings settings; - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - const size_t settings_size = sizeof(NotificationSettings); - - FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH); - bool fs_result = - storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); - - if(fs_result) { - size_t bytes_count = storage_file_read(file, &settings, settings_size); - - if(bytes_count != settings_size) { - fs_result = false; + while(notification_message != NULL) { + switch(notification_message->type) { + case NotificationMessageTypeLedDisplayBacklight: + notification_apply_internal_led_layer( + &app->display, + notification_settings_get_display_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedRed: + app->led[0].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[0], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedGreen: + app->led[1].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[1], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedBlue: + app->led[2].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[2], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedBrightnessSettingApply: + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + uint8_t new_val = notification_settings_get_rgb_led_brightness( + app, app->led[i].value_last[LayerInternal]); + notification_apply_internal_led_layer(&app->led[i], new_val); + } + break; + default: + break; + } + notification_message_index++; + notification_message = (*message->sequence)[notification_message_index]; } } - if(fs_result) { - if(settings.version != NOTIFICATION_SETTINGS_VERSION) { - FURI_LOG_E( - TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION); + static bool notification_load_settings(NotificationApp * app) { + NotificationSettings settings; + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + const size_t settings_size = sizeof(NotificationSettings); + + FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH); + bool fs_result = + storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); + + if(fs_result) { + size_t bytes_count = storage_file_read(file, &settings, settings_size); + + if(bytes_count != settings_size) { + fs_result = false; + } + } + + if(fs_result) { + if(settings.version != NOTIFICATION_SETTINGS_VERSION) { + FURI_LOG_E( + TAG, + "version(%d != %d) mismatch", + settings.version, + NOTIFICATION_SETTINGS_VERSION); + } else { + furi_kernel_lock(); + memcpy(&app->settings, &settings, settings_size); + furi_kernel_unlock(); + } } else { - furi_kernel_lock(); - memcpy(&app->settings, &settings, settings_size); - furi_kernel_unlock(); + FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file)); } - } else { - FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file)); + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return fs_result; } - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); + static bool notification_save_settings(NotificationApp * app) { + NotificationSettings settings; + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + const size_t settings_size = sizeof(NotificationSettings); - return fs_result; -} + FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH); -static bool notification_save_settings(NotificationApp* app) { - NotificationSettings settings; - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - const size_t settings_size = sizeof(NotificationSettings); + furi_kernel_lock(); + memcpy(&settings, &app->settings, settings_size); + furi_kernel_unlock(); - FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH); + bool fs_result = + storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); - furi_kernel_lock(); - memcpy(&settings, &app->settings, settings_size); - furi_kernel_unlock(); + if(fs_result) { + size_t bytes_count = storage_file_write(file, &settings, settings_size); - bool fs_result = - storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); - - if(fs_result) { - size_t bytes_count = storage_file_write(file, &settings, settings_size); - - if(bytes_count != settings_size) { - fs_result = false; + if(bytes_count != settings_size) { + fs_result = false; + } } - } - if(fs_result) { - } else { - FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file)); - } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return fs_result; -} - -static void input_event_callback(const void* value, void* context) { - furi_assert(value); - furi_assert(context); - NotificationApp* app = context; - notification_message(app, &sequence_display_backlight_on); -} - -// App alloc -static NotificationApp* notification_app_alloc(void) { - NotificationApp* app = malloc(sizeof(NotificationApp)); - app->queue = furi_message_queue_alloc(8, sizeof(NotificationAppMessage)); - app->display_timer = furi_timer_alloc(notification_display_timer, FuriTimerTypeOnce, app); - - app->settings.speaker_volume = 1.0f; - app->settings.display_brightness = 1.0f; - app->settings.led_brightness = 1.0f; - app->settings.display_off_delay_ms = 30000; - app->settings.vibro_on = true; - - app->display.value[LayerInternal] = 0x00; - app->display.value[LayerNotification] = 0x00; - app->display.index = LayerInternal; - app->display.light = LightBacklight; - - app->led[0].value[LayerInternal] = 0x00; - app->led[0].value[LayerNotification] = 0x00; - app->led[0].index = LayerInternal; - app->led[0].light = LightRed; - - app->led[1].value[LayerInternal] = 0x00; - app->led[1].value[LayerNotification] = 0x00; - app->led[1].index = LayerInternal; - app->led[1].light = LightGreen; - - app->led[2].value[LayerInternal] = 0x00; - app->led[2].value[LayerNotification] = 0x00; - app->led[2].index = LayerInternal; - app->led[2].light = LightBlue; - - app->settings.version = NOTIFICATION_SETTINGS_VERSION; - - // display backlight control - app->event_record = furi_record_open(RECORD_INPUT_EVENTS); - furi_pubsub_subscribe(app->event_record, input_event_callback, app); - notification_message(app, &sequence_display_backlight_on); - - // --- NIGHT SHIFT --- - app->current_night_shift = 1.0f; - app->current_night_shift = 1.0f; - app->settings.night_shift = 1.0f; - app->settings.night_shift_start = 1020; - app->settings.night_shift_end = 300; - app->night_shift_timer = - furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); - // --- NIGHT SHIFT END --- - - // init working variables - app->rainbow_hue = 1; - app->current_night_shift = 1.0f; - - // init rgb.segings values - app->settings.rgb.rgb_backlight_installed = 0; - app->settings.rgb.led_2_color_index = 0; - app->settings.rgb.led_1_color_index = 0; - app->settings.rgb.led_0_color_index = 0; - app->settings.rgb.rainbow_speed_ms = 100; - app->settings.rgb.rainbow_step = 1; - app->settings.rgb.rainbow_saturation = 255; - app->settings.rgb.rainbow_wide = 50; - - // set inital value, later it will be rewriten by loading settings from file - app->settings.lcd_inversion = false; - - return app; -} - -static void notification_storage_callback(const void* message, void* context) { - furi_assert(context); - NotificationApp* app = context; - const StorageEvent* event = message; - - if(event->type == StorageEventTypeCardMount) { - NotificationAppMessage m = { - .type = LoadSettingsMessage, - }; - - furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); - } -} - -static void notification_apply_settings(NotificationApp* app) { - if(!notification_load_settings(app)) { - notification_save_settings(app); - } - - notification_apply_lcd_contrast(app); - - // --- NIGHT SHIFT --- - // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time - if(app->settings.night_shift != 1) { - night_shift_timer_start(app); - } - // --- NIGHT SHIFT END --- - - // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; - if(furi_record_exists(RECORD_GUI)) { - Gui* gui = furi_record_open(RECORD_GUI); - u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); - furi_record_close(RECORD_GUI); - } -} - -static void notification_init_settings(NotificationApp* app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app); - - if(storage_sd_status(storage) != FSE_OK) { - FURI_LOG_D(TAG, "SD Card not ready, skipping settings"); - return; - } - - notification_apply_settings(app); -} - -// App -int32_t notification_srv(void* p) { - UNUSED(p); - NotificationApp* app = notification_app_alloc(); - - notification_init_settings(app); - - notification_vibro_off(); - notification_sound_off(); - notification_apply_internal_led_layer(&app->display, 0x00); - notification_apply_internal_led_layer(&app->led[0], 0x00); - notification_apply_internal_led_layer(&app->led[1], 0x00); - notification_apply_internal_led_layer(&app->led[2], 0x00); - - furi_record_create(RECORD_NOTIFICATION, app); - - // --- RGB BACKLIGHT SECTION --- - - //setup local variable - set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); - - // define rainbow_timer and they callback - app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); - - // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) - if(app->settings.rgb.rgb_backlight_installed) { - if(app->settings.rgb.rainbow_mode > 0) { - rainbow_timer_start(app); + if(fs_result) { } else { - rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); - rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); - rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); - rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); + FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file)); } - // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on - } else { - rgb_backlight_set_led_static_color(2, 0); - rgb_backlight_set_led_static_color(1, 0); - rgb_backlight_set_led_static_color(0, 0); - SK6805_update(); + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return fs_result; } - // --- RGB BACKLIGHT SECTION END --- + static void input_event_callback(const void* value, void* context) { + furi_assert(value); + furi_assert(context); + NotificationApp* app = context; + notification_message(app, &sequence_display_backlight_on); + } - NotificationAppMessage message; - while(1) { - furi_check(furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); + // App alloc + static NotificationApp* notification_app_alloc(void) { + NotificationApp* app = malloc(sizeof(NotificationApp)); + app->queue = furi_message_queue_alloc(8, sizeof(NotificationAppMessage)); + app->display_timer = furi_timer_alloc(notification_display_timer, FuriTimerTypeOnce, app); - switch(message.type) { - case NotificationLayerMessage: - notification_process_notification_message(app, &message); - break; - case InternalLayerMessage: - notification_process_internal_message(app, &message); - break; - case SaveSettingsMessage: + app->settings.speaker_volume = 1.0f; + app->settings.display_brightness = 1.0f; + app->settings.led_brightness = 1.0f; + app->settings.display_off_delay_ms = 30000; + app->settings.vibro_on = true; + + app->display.value[LayerInternal] = 0x00; + app->display.value[LayerNotification] = 0x00; + app->display.index = LayerInternal; + app->display.light = LightBacklight; + + app->led[0].value[LayerInternal] = 0x00; + app->led[0].value[LayerNotification] = 0x00; + app->led[0].index = LayerInternal; + app->led[0].light = LightRed; + + app->led[1].value[LayerInternal] = 0x00; + app->led[1].value[LayerNotification] = 0x00; + app->led[1].index = LayerInternal; + app->led[1].light = LightGreen; + + app->led[2].value[LayerInternal] = 0x00; + app->led[2].value[LayerNotification] = 0x00; + app->led[2].index = LayerInternal; + app->led[2].light = LightBlue; + + app->settings.version = NOTIFICATION_SETTINGS_VERSION; + + // display backlight control + app->event_record = furi_record_open(RECORD_INPUT_EVENTS); + furi_pubsub_subscribe(app->event_record, input_event_callback, app); + notification_message(app, &sequence_display_backlight_on); + + // --- NIGHT SHIFT --- + app->current_night_shift = 1.0f; + app->current_night_shift = 1.0f; + app->settings.night_shift = 1.0f; + app->settings.night_shift_start = 1020; + app->settings.night_shift_end = 300; + app->night_shift_timer = + furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); + // --- NIGHT SHIFT END --- + + // init working variables + app->rainbow_hue = 1; + app->current_night_shift = 1.0f; + + // init rgb.segings values + app->settings.rgb.rgb_backlight_installed = 0; + app->settings.rgb.led_2_color_index = 0; + app->settings.rgb.led_1_color_index = 0; + app->settings.rgb.led_0_color_index = 0; + app->settings.rgb.rainbow_speed_ms = 100; + app->settings.rgb.rainbow_step = 1; + app->settings.rgb.rainbow_saturation = 255; + app->settings.rgb.rainbow_wide = 50; + + // set inital value, later it will be rewriten by loading settings from file + app->settings.lcd_inversion = false; + + return app; + } + + static void notification_storage_callback(const void* message, void* context) { + furi_assert(context); + NotificationApp* app = context; + const StorageEvent* event = message; + + if(event->type == StorageEventTypeCardMount) { + NotificationAppMessage m = { + .type = LoadSettingsMessage, + }; + + furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); + } + } + + static void notification_apply_settings(NotificationApp * app) { + if(!notification_load_settings(app)) { notification_save_settings(app); - break; - case LoadSettingsMessage: - notification_load_settings(app); - break; } - if(message.back_event != NULL) { - furi_event_flag_set(message.back_event, NOTIFICATION_EVENT_COMPLETE); + notification_apply_lcd_contrast(app); + + // --- NIGHT SHIFT --- + // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time + if(app->settings.night_shift != 1) { + night_shift_timer_start(app); + } + // --- NIGHT SHIFT END --- + + // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; + if(furi_record_exists(RECORD_GUI)) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); + furi_record_close(RECORD_GUI); } } - return 0; -} + static void notification_init_settings(NotificationApp * app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app); + + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_D(TAG, "SD Card not ready, skipping settings"); + return; + } + + notification_apply_settings(app); + } + + // App + int32_t notification_srv(void* p) { + UNUSED(p); + NotificationApp* app = notification_app_alloc(); + + notification_init_settings(app); + + notification_vibro_off(); + notification_sound_off(); + notification_apply_internal_led_layer(&app->display, 0x00); + notification_apply_internal_led_layer(&app->led[0], 0x00); + notification_apply_internal_led_layer(&app->led[1], 0x00); + notification_apply_internal_led_layer(&app->led[2], 0x00); + + furi_record_create(RECORD_NOTIFICATION, app); + + // --- RGB BACKLIGHT SECTION --- + + //setup local variable + set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); + + // define rainbow_timer and they callback + app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); + + // define night_shift_demo_timer and they callback. + // used for Setting menu to demonstrate night_shift_backlight when user change value + app->night_shift_demo_timer = + furi_timer_alloc(night_shift_demo_timer_callback, FuriTimerTypeOnce, app); + + // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) + if(app->settings.rgb.rgb_backlight_installed) { + if(app->settings.rgb.rainbow_mode > 0) { + rainbow_timer_start(app); + } else { + rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); + rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); + rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); + rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); + } + // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on + } else { + rgb_backlight_set_led_static_color(2, 0); + rgb_backlight_set_led_static_color(1, 0); + rgb_backlight_set_led_static_color(0, 0); + SK6805_update(); + } + + // --- RGB BACKLIGHT SECTION END --- + + NotificationAppMessage message; + while(1) { + furi_check( + furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); + + switch(message.type) { + case NotificationLayerMessage: + notification_process_notification_message(app, &message); + break; + case InternalLayerMessage: + notification_process_internal_message(app, &message); + break; + case SaveSettingsMessage: + notification_save_settings(app); + break; + case LoadSettingsMessage: + notification_load_settings(app); + break; + } + + if(message.back_event != NULL) { + furi_event_flag_set(message.back_event, NOTIFICATION_EVENT_COMPLETE); + } + } + + return 0; + } diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index abf3cc15e..111fbd481 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -86,6 +86,7 @@ struct NotificationApp { NotificationSettings settings; FuriTimer* night_shift_timer; + FuriTimer* night_shift_demo_timer; float current_night_shift; FuriTimer* rainbow_timer; diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 4d95a8ac6..84a2747f7 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -558,10 +558,6 @@ static void night_shift_changed(VariableItem* item) { variable_item_set_current_value_text(item, night_shift_text[index]); app->notification->settings.night_shift = night_shift_value[index]; - // force demo night_shift brightness to rgb backlight and stock backlight - // app->notification->current_night_shift = night_shift_value[index]; - // notification_message(app->notification, &sequence_display_backlight_force_on); - for(int i = 4; i < 6; i++) { VariableItem* t_item = variable_item_list_get(app->variable_item_list, i); if(index == 0) { @@ -571,10 +567,22 @@ static void night_shift_changed(VariableItem* item) { } } + // force demo night_shift brightness to rgb backlight and stock backlight for 1,2 sec + // while 1,2 seconds are running, there is another timer "night_shift_timer" can change current_night_shift to day or night value + // so when night_shift_demo_timer ended backlight force ON to day or night brightness + app->notification->current_night_shift = night_shift_value[index]; + notification_message(app->notification, &sequence_display_backlight_force_on); + if(night_shift_value[index] != 1) { night_shift_timer_start(app->notification); + if(furi_timer_is_running(app->notification->night_shift_demo_timer)) { + furi_timer_stop(app->notification->night_shift_demo_timer); + } + furi_timer_start(app->notification->night_shift_demo_timer, furi_ms_to_ticks(1200)); } else { night_shift_timer_stop(app->notification); + if(furi_timer_is_running(app->notification->night_shift_demo_timer)) + furi_timer_stop(app->notification->night_shift_demo_timer); } notification_message_save_settings(app->notification); From ed5c87fb144c355204c5edfb6300b5e757a7613e Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 8 Feb 2026 18:39:28 +0700 Subject: [PATCH 050/202] backlight time correction --- applications/services/notification/notification_app.h | 2 +- .../notification_settings/notification_settings_app.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index 111fbd481..094027d89 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -39,7 +39,7 @@ typedef struct { Light light; } NotificationLedLayer; -#define NOTIFICATION_SETTINGS_VERSION 0x05 +#define NOTIFICATION_SETTINGS_VERSION 0x06 #define NOTIFICATION_SETTINGS_PATH INT_PATH(NOTIFICATION_SETTINGS_FILE_NAME) typedef struct { diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index 84a2747f7..e82b02c87 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -87,7 +87,7 @@ const float volume_value[VOLUME_COUNT] = { #define DELAY_COUNT 12 const char* const delay_text[DELAY_COUNT] = { "Always ON", - "1s", + "2s", "5s", "10s", "15s", @@ -100,7 +100,7 @@ const char* const delay_text[DELAY_COUNT] = { "30min", }; const uint32_t delay_value[DELAY_COUNT] = - {0, 1000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; + {0, 2000, 5000, 10000, 15000, 30000, 60000, 90000, 120000, 300000, 600000, 1800000}; #define VIBRO_COUNT 2 const char* const vibro_text[VIBRO_COUNT] = { From 7bb166e2acdc20327da205bd8117b5708b83b96c Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 8 Feb 2026 19:00:52 +0700 Subject: [PATCH 051/202] "kostylling" ))) --- .../services/notification/notification_app.c | 1267 +++++++++-------- .../services/notification/notification_app.h | 2 +- 2 files changed, 635 insertions(+), 634 deletions(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index d3a0e65bc..8a62f7ac9 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -283,717 +283,718 @@ void night_shift_demo_timer_callback(void* context) { NotificationApp* app = context; notification_message(app, &sequence_display_backlight_force_on); } - // --- NIGHT SHIFT END --- +// --- NIGHT SHIFT END --- - void notification_message_save_settings(NotificationApp * app) { - NotificationAppMessage m = { - .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; - furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); - furi_event_flag_wait( - m.back_event, NOTIFICATION_EVENT_COMPLETE, FuriFlagWaitAny, FuriWaitForever); - furi_event_flag_free(m.back_event); - } +void notification_message_save_settings(NotificationApp* app) { + NotificationAppMessage m = { + .type = SaveSettingsMessage, .back_event = furi_event_flag_alloc()}; + furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); + furi_event_flag_wait( + m.back_event, NOTIFICATION_EVENT_COMPLETE, FuriFlagWaitAny, FuriWaitForever); + furi_event_flag_free(m.back_event); +} - // internal layer - static void notification_apply_internal_led_layer( - NotificationLedLayer * layer, uint8_t layer_value) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); +// internal layer +static void + notification_apply_internal_led_layer(NotificationLedLayer* layer, uint8_t layer_value) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); - // set value - layer->value[LayerInternal] = layer_value; + // set value + layer->value[LayerInternal] = layer_value; - // apply if current layer is internal - if(layer->index == LayerInternal) { - furi_hal_light_set(layer->light, layer->value[LayerInternal]); - } - } - - static void notification_apply_lcd_contrast(NotificationApp * app) { - Gui* gui = furi_record_open(RECORD_GUI); - u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); - furi_record_close(RECORD_GUI); - } - - static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp * app) { - bool result = false; - if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || - (app->led[2].index == LayerInternal)) { - if((app->led[0].value[LayerInternal] != 0x00) || - (app->led[1].value[LayerInternal] != 0x00) || - (app->led[2].value[LayerInternal] != 0x00)) { - result = true; - } - } - - return result; - } - - // notification layer - static void notification_apply_notification_led_layer( - NotificationLedLayer * layer, const uint8_t layer_value) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); - - // set value - layer->index = LayerNotification; - // set layer - layer->value[LayerNotification] = layer_value; - - // if layer.light = LightBacklight and backlight active now then just exit. - // prevent from extra ticking when key pressed with rgb_mod_installed - if((layer->light == LightBacklight) & lcd_backlight_is_on) return; - - // apply - furi_hal_light_set(layer->light, layer->value[LayerNotification]); - } - - static void notification_reset_notification_led_layer(NotificationLedLayer * layer) { - furi_assert(layer); - furi_assert(layer->index < LayerMAX); - - // set value - layer->value[LayerNotification] = 0; - // set layer - layer->index = LayerInternal; - - // apply + // apply if current layer is internal + if(layer->index == LayerInternal) { furi_hal_light_set(layer->light, layer->value[LayerInternal]); } +} - static void notification_reset_notification_layer( - NotificationApp * app, uint8_t reset_mask, float display_brightness_set) { - if(reset_mask & reset_blink_mask) { - furi_hal_light_blink_stop(); - } - if(reset_mask & reset_red_mask) { - notification_reset_notification_led_layer(&app->led[0]); - } - if(reset_mask & reset_green_mask) { - notification_reset_notification_led_layer(&app->led[1]); - } - if(reset_mask & reset_blue_mask) { - notification_reset_notification_led_layer(&app->led[2]); - } - if(reset_mask & reset_vibro_mask) { - notification_vibro_off(); - } - if(reset_mask & reset_sound_mask) { - notification_sound_off(); - } - if(reset_mask & reset_display_mask) { - if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { - furi_hal_light_set( - LightBacklight, - app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); - } - if(app->settings.display_off_delay_ms > 0) { - furi_timer_start( - app->display_timer, notification_settings_display_off_delay_ticks(app)); - } +static void notification_apply_lcd_contrast(NotificationApp* app) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_contrast(&gui->canvas->fb.u8x8, app->settings.contrast); + furi_record_close(RECORD_GUI); +} + +static bool notification_is_any_led_layer_internal_and_not_empty(NotificationApp* app) { + bool result = false; + if((app->led[0].index == LayerInternal) || (app->led[1].index == LayerInternal) || + (app->led[2].index == LayerInternal)) { + if((app->led[0].value[LayerInternal] != 0x00) || + (app->led[1].value[LayerInternal] != 0x00) || + (app->led[2].value[LayerInternal] != 0x00)) { + result = true; } } - static void notification_apply_notification_leds( - NotificationApp * app, const uint8_t* values) { - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - notification_apply_notification_led_layer( - &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); + return result; +} + +// notification layer +static void notification_apply_notification_led_layer( + NotificationLedLayer* layer, + const uint8_t layer_value) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); + + // set value + layer->index = LayerNotification; + // set layer + layer->value[LayerNotification] = layer_value; + + // if layer.light = LightBacklight and backlight active now then just exit. + // prevent from extra ticking when key pressed with rgb_mod_installed + if((layer->light == LightBacklight) & lcd_backlight_is_on) return; + + // apply + furi_hal_light_set(layer->light, layer->value[LayerNotification]); +} + +static void notification_reset_notification_led_layer(NotificationLedLayer* layer) { + furi_assert(layer); + furi_assert(layer->index < LayerMAX); + + // set value + layer->value[LayerNotification] = 0; + // set layer + layer->index = LayerInternal; + + // apply + furi_hal_light_set(layer->light, layer->value[LayerInternal]); +} + +static void notification_reset_notification_layer( + NotificationApp* app, + uint8_t reset_mask, + float display_brightness_set) { + if(reset_mask & reset_blink_mask) { + furi_hal_light_blink_stop(); + } + if(reset_mask & reset_red_mask) { + notification_reset_notification_led_layer(&app->led[0]); + } + if(reset_mask & reset_green_mask) { + notification_reset_notification_led_layer(&app->led[1]); + } + if(reset_mask & reset_blue_mask) { + notification_reset_notification_led_layer(&app->led[2]); + } + if(reset_mask & reset_vibro_mask) { + notification_vibro_off(); + } + if(reset_mask & reset_sound_mask) { + notification_sound_off(); + } + if(reset_mask & reset_display_mask) { + if(!float_is_equal(display_brightness_set, app->settings.display_brightness)) { + furi_hal_light_set( + LightBacklight, + app->settings.display_brightness * 0xFF * app->current_night_shift * 1.0f); + } + if(app->settings.display_off_delay_ms > 0) { + furi_timer_start( + app->display_timer, notification_settings_display_off_delay_ticks(app)); } } +} - // settings - uint8_t notification_settings_get_display_brightness(NotificationApp * app, uint8_t value) { - return value * app->settings.display_brightness; +static void notification_apply_notification_leds(NotificationApp* app, const uint8_t* values) { + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + notification_apply_notification_led_layer( + &app->led[i], notification_settings_get_rgb_led_brightness(app, values[i])); } +} - static uint8_t notification_settings_get_rgb_led_brightness( - NotificationApp * app, uint8_t value) { - return value * app->settings.led_brightness; +// settings +uint8_t notification_settings_get_display_brightness(NotificationApp* app, uint8_t value) { + return value * app->settings.display_brightness; +} + +static uint8_t notification_settings_get_rgb_led_brightness(NotificationApp* app, uint8_t value) { + return value * app->settings.led_brightness; +} + +static uint32_t notification_settings_display_off_delay_ticks(NotificationApp* app) { + return (float)(app->settings.display_off_delay_ms) / + (1000.0f / furi_kernel_get_tick_frequency()); +} + +// generics +static void notification_vibro_on(bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + furi_hal_vibro_on(true); } +} - static uint32_t notification_settings_display_off_delay_ticks(NotificationApp * app) { - return (float)(app->settings.display_off_delay_ms) / - (1000.0f / furi_kernel_get_tick_frequency()); - } +static void notification_vibro_off(void) { + furi_hal_vibro_on(false); +} - // generics - static void notification_vibro_on(bool force) { - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { - furi_hal_vibro_on(true); +static void notification_sound_on(float freq, float volume, bool force) { + if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { + if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { + furi_hal_speaker_start(freq, volume); } } +} - static void notification_vibro_off(void) { - furi_hal_vibro_on(false); +static void notification_sound_off(void) { + if(furi_hal_speaker_is_mine()) { + furi_hal_speaker_stop(); + furi_hal_speaker_release(); } +} - static void notification_sound_on(float freq, float volume, bool force) { - if(!furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode) || force) { - if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) { - furi_hal_speaker_start(freq, volume); - } - } - } +// display timer +static void notification_display_timer(void* ctx) { + furi_assert(ctx); + NotificationApp* app = ctx; + notification_message(app, &sequence_display_backlight_off); +} - static void notification_sound_off(void) { - if(furi_hal_speaker_is_mine()) { - furi_hal_speaker_stop(); - furi_hal_speaker_release(); - } - } +// message processing +static void notification_process_notification_message( + NotificationApp* app, + NotificationAppMessage* message) { + uint32_t notification_message_index = 0; + bool force_volume = false; + bool force_vibro = false; + const NotificationMessage* notification_message; + notification_message = (*message->sequence)[notification_message_index]; - // display timer - static void notification_display_timer(void* ctx) { - furi_assert(ctx); - NotificationApp* app = ctx; - notification_message(app, &sequence_display_backlight_off); - } + bool led_active = false; + uint8_t led_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; + bool reset_notifications = true; + float speaker_volume_setting = app->settings.speaker_volume; + bool vibro_setting = app->settings.vibro_on; + float display_brightness_setting = app->settings.display_brightness; - // message processing - static void notification_process_notification_message( - NotificationApp * app, NotificationAppMessage * message) { - uint32_t notification_message_index = 0; - bool force_volume = false; - bool force_vibro = false; - const NotificationMessage* notification_message; - notification_message = (*message->sequence)[notification_message_index]; + uint8_t reset_mask = 0; - bool led_active = false; - uint8_t led_values[NOTIFICATION_LED_COUNT] = {0x00, 0x00, 0x00}; - bool reset_notifications = true; - float speaker_volume_setting = app->settings.speaker_volume; - bool vibro_setting = app->settings.vibro_on; - float display_brightness_setting = app->settings.display_brightness; - - uint8_t reset_mask = 0; - - while(notification_message != NULL) { - switch(notification_message->type) { - case NotificationMessageTypeLedDisplayBacklight: - // if on (data.led.value =0xFF) - switch on and start timer - // if off (data.led.value =0x0) - switch off and stop timer - if(notification_message->data.led.value > 0x00) { - // Backlight ON - notification_apply_notification_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - - reset_mask |= reset_display_mask; - lcd_backlight_is_on = true; - - //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too - rainbow_timer_starter(app); - - } else { - // Backlight OFF - reset_mask &= ~reset_display_mask; - notification_reset_notification_led_layer(&app->display); - lcd_backlight_is_on = false; - - if(furi_timer_is_running(app->display_timer)) { - furi_timer_stop(app->display_timer); - } - - //stop rgb_mod_rainbow_timer when display backlight is OFF - if(furi_timer_is_running(app->rainbow_timer)) { - rainbow_timer_stop(app); - } - } - break; - case NotificationMessageTypeLedDisplayBacklightForceOn: - // Force Backlight ON even if its ON now - lcd_backlight_is_on = false; + while(notification_message != NULL) { + switch(notification_message->type) { + case NotificationMessageTypeLedDisplayBacklight: + // if on (data.led.value =0xFF) - switch on and start timer + // if off (data.led.value =0x0) - switch off and stop timer + if(notification_message->data.led.value > 0x00) { + // Backlight ON notification_apply_notification_led_layer( &app->display, notification_message->data.led.value * display_brightness_setting * app->current_night_shift * 1.0f); + reset_mask |= reset_display_mask; lcd_backlight_is_on = true; //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too rainbow_timer_starter(app); - break; - case NotificationMessageTypeLedDisplayBacklightEnforceOn: - if(!app->display_led_lock) { - app->display_led_lock = true; - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - lcd_backlight_is_on = true; - } - break; - case NotificationMessageTypeLedDisplayBacklightEnforceAuto: - if(app->display_led_lock) { - app->display_led_lock = false; - notification_apply_internal_led_layer( - &app->display, - notification_message->data.led.value * display_brightness_setting * - app->current_night_shift * 1.0f); - // --- NIGHT SHIFT END --- - } else { - FURI_LOG_E(TAG, "Incorrect BacklightEnforceAuto usage"); - } - break; - case NotificationMessageTypeLedRed: - // store and send on delay or after seq - led_active = true; - led_values[0] = notification_message->data.led.value; - app->led[0].value_last[LayerNotification] = led_values[0]; - reset_mask |= reset_red_mask; - break; - case NotificationMessageTypeLedGreen: - // store and send on delay or after seq - led_active = true; - led_values[1] = notification_message->data.led.value; - app->led[1].value_last[LayerNotification] = led_values[1]; - reset_mask |= reset_green_mask; - break; - case NotificationMessageTypeLedBlue: - // store and send on delay or after seq - led_active = true; - led_values[2] = notification_message->data.led.value; - app->led[2].value_last[LayerNotification] = led_values[2]; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLedBlinkStart: - // store and send on delay or after seq - led_active = true; - furi_hal_light_blink_start( - notification_message->data.led_blink.color, - app->settings.led_brightness * 255, - notification_message->data.led_blink.on_time, - notification_message->data.led_blink.period); - reset_mask |= reset_blink_mask; - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLedBlinkColor: - led_active = true; - furi_hal_light_blink_set_color(notification_message->data.led_blink.color); - break; - case NotificationMessageTypeLedBlinkStop: - furi_hal_light_blink_stop(); - reset_mask &= ~reset_blink_mask; - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeVibro: - if(notification_message->data.vibro.on) { - if(vibro_setting) notification_vibro_on(force_vibro); - } else { - notification_vibro_off(); - } - reset_mask |= reset_vibro_mask; - break; - case NotificationMessageTypeSoundOn: - notification_sound_on( - notification_message->data.sound.frequency, - notification_message->data.sound.volume * speaker_volume_setting, - force_volume); - reset_mask |= reset_sound_mask; - break; - case NotificationMessageTypeSoundOff: - notification_sound_off(); - reset_mask |= reset_sound_mask; - break; - case NotificationMessageTypeDelay: - if(led_active) { - if(notification_is_any_led_layer_internal_and_not_empty(app)) { - notification_apply_notification_leds(app, led_off_values); - furi_delay_ms(minimal_delay); - } - led_active = false; + } else { + // Backlight OFF + reset_mask &= ~reset_display_mask; + notification_reset_notification_led_layer(&app->display); + lcd_backlight_is_on = false; - notification_apply_notification_leds(app, led_values); - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; + if(furi_timer_is_running(app->display_timer)) { + furi_timer_stop(app->display_timer); } - furi_delay_ms(notification_message->data.delay.length); - break; - case NotificationMessageTypeDoNotReset: - reset_notifications = false; - break; - case NotificationMessageTypeForceSpeakerVolumeSetting: - speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; - force_volume = true; - break; - case NotificationMessageTypeForceVibroSetting: - vibro_setting = notification_message->data.forced_settings.vibro; - force_vibro = true; - break; - case NotificationMessageTypeForceDisplayBrightnessSetting: - display_brightness_setting = - notification_message->data.forced_settings.display_brightness; - break; - case NotificationMessageTypeLedBrightnessSettingApply: - led_active = true; - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - led_values[i] = app->led[i].value_last[LayerNotification]; + //stop rgb_mod_rainbow_timer when display backlight is OFF + if(furi_timer_is_running(app->rainbow_timer)) { + rainbow_timer_stop(app); } - reset_mask |= reset_red_mask; - reset_mask |= reset_green_mask; - reset_mask |= reset_blue_mask; - break; - case NotificationMessageTypeLcdContrastUpdate: - notification_apply_lcd_contrast(app); - break; } - notification_message_index++; - notification_message = (*message->sequence)[notification_message_index]; - }; + break; + case NotificationMessageTypeLedDisplayBacklightForceOn: + // Force Backlight ON even if its ON now + lcd_backlight_is_on = false; + notification_apply_notification_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + reset_mask |= reset_display_mask; + lcd_backlight_is_on = true; - // send and do minimal delay - if(led_active) { - bool need_minimal_delay = false; - if(notification_is_any_led_layer_internal_and_not_empty(app)) { - need_minimal_delay = true; + //start rgb_mod_rainbow_timer when display backlight is ON and all corresponding settings is ON too + rainbow_timer_starter(app); + break; + case NotificationMessageTypeLedDisplayBacklightEnforceOn: + if(!app->display_led_lock) { + app->display_led_lock = true; + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + lcd_backlight_is_on = true; } - - notification_apply_notification_leds(app, led_values); + break; + case NotificationMessageTypeLedDisplayBacklightEnforceAuto: + if(app->display_led_lock) { + app->display_led_lock = false; + notification_apply_internal_led_layer( + &app->display, + notification_message->data.led.value * display_brightness_setting * + app->current_night_shift * 1.0f); + // --- NIGHT SHIFT END --- + } else { + FURI_LOG_E(TAG, "Incorrect BacklightEnforceAuto usage"); + } + break; + case NotificationMessageTypeLedRed: + // store and send on delay or after seq + led_active = true; + led_values[0] = notification_message->data.led.value; + app->led[0].value_last[LayerNotification] = led_values[0]; + reset_mask |= reset_red_mask; + break; + case NotificationMessageTypeLedGreen: + // store and send on delay or after seq + led_active = true; + led_values[1] = notification_message->data.led.value; + app->led[1].value_last[LayerNotification] = led_values[1]; + reset_mask |= reset_green_mask; + break; + case NotificationMessageTypeLedBlue: + // store and send on delay or after seq + led_active = true; + led_values[2] = notification_message->data.led.value; + app->led[2].value_last[LayerNotification] = led_values[2]; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeLedBlinkStart: + // store and send on delay or after seq + led_active = true; + furi_hal_light_blink_start( + notification_message->data.led_blink.color, + app->settings.led_brightness * 255, + notification_message->data.led_blink.on_time, + notification_message->data.led_blink.period); + reset_mask |= reset_blink_mask; reset_mask |= reset_red_mask; reset_mask |= reset_green_mask; reset_mask |= reset_blue_mask; - - if((need_minimal_delay) && (reset_notifications)) { - notification_apply_notification_leds(app, led_off_values); - furi_delay_ms(minimal_delay); + break; + case NotificationMessageTypeLedBlinkColor: + led_active = true; + furi_hal_light_blink_set_color(notification_message->data.led_blink.color); + break; + case NotificationMessageTypeLedBlinkStop: + furi_hal_light_blink_stop(); + reset_mask &= ~reset_blink_mask; + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeVibro: + if(notification_message->data.vibro.on) { + if(vibro_setting) notification_vibro_on(force_vibro); + } else { + notification_vibro_off(); } - } - - if(reset_notifications) { - notification_reset_notification_layer(app, reset_mask, display_brightness_setting); - } - } - - static void notification_process_internal_message( - NotificationApp * app, NotificationAppMessage * message) { - uint32_t notification_message_index = 0; - const NotificationMessage* notification_message; - notification_message = (*message->sequence)[notification_message_index]; - - while(notification_message != NULL) { - switch(notification_message->type) { - case NotificationMessageTypeLedDisplayBacklight: - notification_apply_internal_led_layer( - &app->display, - notification_settings_get_display_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedRed: - app->led[0].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[0], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedGreen: - app->led[1].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[1], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedBlue: - app->led[2].value_last[LayerInternal] = notification_message->data.led.value; - notification_apply_internal_led_layer( - &app->led[2], - notification_settings_get_rgb_led_brightness( - app, notification_message->data.led.value)); - break; - case NotificationMessageTypeLedBrightnessSettingApply: - for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { - uint8_t new_val = notification_settings_get_rgb_led_brightness( - app, app->led[i].value_last[LayerInternal]); - notification_apply_internal_led_layer(&app->led[i], new_val); + reset_mask |= reset_vibro_mask; + break; + case NotificationMessageTypeSoundOn: + notification_sound_on( + notification_message->data.sound.frequency, + notification_message->data.sound.volume * speaker_volume_setting, + force_volume); + reset_mask |= reset_sound_mask; + break; + case NotificationMessageTypeSoundOff: + notification_sound_off(); + reset_mask |= reset_sound_mask; + break; + case NotificationMessageTypeDelay: + if(led_active) { + if(notification_is_any_led_layer_internal_and_not_empty(app)) { + notification_apply_notification_leds(app, led_off_values); + furi_delay_ms(minimal_delay); } - break; - default: - break; + + led_active = false; + + notification_apply_notification_leds(app, led_values); + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; } - notification_message_index++; - notification_message = (*message->sequence)[notification_message_index]; + + furi_delay_ms(notification_message->data.delay.length); + break; + case NotificationMessageTypeDoNotReset: + reset_notifications = false; + break; + case NotificationMessageTypeForceSpeakerVolumeSetting: + speaker_volume_setting = notification_message->data.forced_settings.speaker_volume; + force_volume = true; + break; + case NotificationMessageTypeForceVibroSetting: + vibro_setting = notification_message->data.forced_settings.vibro; + force_vibro = true; + break; + case NotificationMessageTypeForceDisplayBrightnessSetting: + display_brightness_setting = + notification_message->data.forced_settings.display_brightness; + break; + case NotificationMessageTypeLedBrightnessSettingApply: + led_active = true; + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + led_values[i] = app->led[i].value_last[LayerNotification]; + } + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + break; + case NotificationMessageTypeLcdContrastUpdate: + notification_apply_lcd_contrast(app); + break; + } + notification_message_index++; + notification_message = (*message->sequence)[notification_message_index]; + }; + + // send and do minimal delay + if(led_active) { + bool need_minimal_delay = false; + if(notification_is_any_led_layer_internal_and_not_empty(app)) { + need_minimal_delay = true; + } + + notification_apply_notification_leds(app, led_values); + reset_mask |= reset_red_mask; + reset_mask |= reset_green_mask; + reset_mask |= reset_blue_mask; + + if((need_minimal_delay) && (reset_notifications)) { + notification_apply_notification_leds(app, led_off_values); + furi_delay_ms(minimal_delay); } } - static bool notification_load_settings(NotificationApp * app) { - NotificationSettings settings; - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - const size_t settings_size = sizeof(NotificationSettings); + if(reset_notifications) { + notification_reset_notification_layer(app, reset_mask, display_brightness_setting); + } +} - FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH); - bool fs_result = - storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); +static void + notification_process_internal_message(NotificationApp* app, NotificationAppMessage* message) { + uint32_t notification_message_index = 0; + const NotificationMessage* notification_message; + notification_message = (*message->sequence)[notification_message_index]; - if(fs_result) { - size_t bytes_count = storage_file_read(file, &settings, settings_size); - - if(bytes_count != settings_size) { - fs_result = false; + while(notification_message != NULL) { + switch(notification_message->type) { + case NotificationMessageTypeLedDisplayBacklight: + notification_apply_internal_led_layer( + &app->display, + notification_settings_get_display_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedRed: + app->led[0].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[0], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedGreen: + app->led[1].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[1], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedBlue: + app->led[2].value_last[LayerInternal] = notification_message->data.led.value; + notification_apply_internal_led_layer( + &app->led[2], + notification_settings_get_rgb_led_brightness( + app, notification_message->data.led.value)); + break; + case NotificationMessageTypeLedBrightnessSettingApply: + for(uint8_t i = 0; i < NOTIFICATION_LED_COUNT; i++) { + uint8_t new_val = notification_settings_get_rgb_led_brightness( + app, app->led[i].value_last[LayerInternal]); + notification_apply_internal_led_layer(&app->led[i], new_val); } + break; + default: + break; } + notification_message_index++; + notification_message = (*message->sequence)[notification_message_index]; + } +} - if(fs_result) { - if(settings.version != NOTIFICATION_SETTINGS_VERSION) { - FURI_LOG_E( - TAG, - "version(%d != %d) mismatch", - settings.version, - NOTIFICATION_SETTINGS_VERSION); - } else { - furi_kernel_lock(); - memcpy(&app->settings, &settings, settings_size); - furi_kernel_unlock(); - } +static bool notification_load_settings(NotificationApp* app) { + NotificationSettings settings; + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + const size_t settings_size = sizeof(NotificationSettings); + + FURI_LOG_I(TAG, "Loading \"%s\"", NOTIFICATION_SETTINGS_PATH); + bool fs_result = + storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_READ, FSOM_OPEN_EXISTING); + + if(fs_result) { + size_t bytes_count = storage_file_read(file, &settings, settings_size); + + if(bytes_count != settings_size) { + fs_result = false; + } + } + + if(fs_result) { + if(settings.version != NOTIFICATION_SETTINGS_VERSION) { + FURI_LOG_E( + TAG, "version(%d != %d) mismatch", settings.version, NOTIFICATION_SETTINGS_VERSION); } else { - FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file)); + furi_kernel_lock(); + memcpy(&app->settings, &settings, settings_size); + furi_kernel_unlock(); } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return fs_result; + } else { + FURI_LOG_E(TAG, "Load failed, %s", storage_file_get_error_desc(file)); } - static bool notification_save_settings(NotificationApp * app) { - NotificationSettings settings; - File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); - const size_t settings_size = sizeof(NotificationSettings); + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); - FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH); + // "kostyl" for update old setting to new without change settings version + if(app->settings.display_off_delay_ms < 2000) app->settings.display_off_delay_ms = 2000; + + return fs_result; +} - furi_kernel_lock(); - memcpy(&settings, &app->settings, settings_size); - furi_kernel_unlock(); +static bool notification_save_settings(NotificationApp* app) { + NotificationSettings settings; + File* file = storage_file_alloc(furi_record_open(RECORD_STORAGE)); + const size_t settings_size = sizeof(NotificationSettings); - bool fs_result = - storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); + FURI_LOG_I(TAG, "Saving \"%s\"", NOTIFICATION_SETTINGS_PATH); - if(fs_result) { - size_t bytes_count = storage_file_write(file, &settings, settings_size); + furi_kernel_lock(); + memcpy(&settings, &app->settings, settings_size); + furi_kernel_unlock(); - if(bytes_count != settings_size) { - fs_result = false; - } + bool fs_result = + storage_file_open(file, NOTIFICATION_SETTINGS_PATH, FSAM_WRITE, FSOM_CREATE_ALWAYS); + + if(fs_result) { + size_t bytes_count = storage_file_write(file, &settings, settings_size); + + if(bytes_count != settings_size) { + fs_result = false; } + } - if(fs_result) { + if(fs_result) { + } else { + FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file)); + } + + storage_file_close(file); + storage_file_free(file); + furi_record_close(RECORD_STORAGE); + + return fs_result; +} + +static void input_event_callback(const void* value, void* context) { + furi_assert(value); + furi_assert(context); + NotificationApp* app = context; + notification_message(app, &sequence_display_backlight_on); +} + +// App alloc +static NotificationApp* notification_app_alloc(void) { + NotificationApp* app = malloc(sizeof(NotificationApp)); + app->queue = furi_message_queue_alloc(8, sizeof(NotificationAppMessage)); + app->display_timer = furi_timer_alloc(notification_display_timer, FuriTimerTypeOnce, app); + + app->settings.speaker_volume = 1.0f; + app->settings.display_brightness = 1.0f; + app->settings.led_brightness = 1.0f; + app->settings.display_off_delay_ms = 30000; + app->settings.vibro_on = true; + + app->display.value[LayerInternal] = 0x00; + app->display.value[LayerNotification] = 0x00; + app->display.index = LayerInternal; + app->display.light = LightBacklight; + + app->led[0].value[LayerInternal] = 0x00; + app->led[0].value[LayerNotification] = 0x00; + app->led[0].index = LayerInternal; + app->led[0].light = LightRed; + + app->led[1].value[LayerInternal] = 0x00; + app->led[1].value[LayerNotification] = 0x00; + app->led[1].index = LayerInternal; + app->led[1].light = LightGreen; + + app->led[2].value[LayerInternal] = 0x00; + app->led[2].value[LayerNotification] = 0x00; + app->led[2].index = LayerInternal; + app->led[2].light = LightBlue; + + app->settings.version = NOTIFICATION_SETTINGS_VERSION; + + // display backlight control + app->event_record = furi_record_open(RECORD_INPUT_EVENTS); + furi_pubsub_subscribe(app->event_record, input_event_callback, app); + notification_message(app, &sequence_display_backlight_on); + + // --- NIGHT SHIFT --- + app->current_night_shift = 1.0f; + app->current_night_shift = 1.0f; + app->settings.night_shift = 1.0f; + app->settings.night_shift_start = 1020; + app->settings.night_shift_end = 300; + app->night_shift_timer = + furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); + // --- NIGHT SHIFT END --- + + // init working variables + app->rainbow_hue = 1; + app->current_night_shift = 1.0f; + + // init rgb.segings values + app->settings.rgb.rgb_backlight_installed = 0; + app->settings.rgb.led_2_color_index = 0; + app->settings.rgb.led_1_color_index = 0; + app->settings.rgb.led_0_color_index = 0; + app->settings.rgb.rainbow_speed_ms = 100; + app->settings.rgb.rainbow_step = 1; + app->settings.rgb.rainbow_saturation = 255; + app->settings.rgb.rainbow_wide = 50; + + // set inital value, later it will be rewriten by loading settings from file + app->settings.lcd_inversion = false; + + return app; +} + +static void notification_storage_callback(const void* message, void* context) { + furi_assert(context); + NotificationApp* app = context; + const StorageEvent* event = message; + + if(event->type == StorageEventTypeCardMount) { + NotificationAppMessage m = { + .type = LoadSettingsMessage, + }; + + furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); + } +} + +static void notification_apply_settings(NotificationApp* app) { + if(!notification_load_settings(app)) { + notification_save_settings(app); + } + + notification_apply_lcd_contrast(app); + + // --- NIGHT SHIFT --- + // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time + if(app->settings.night_shift != 1) { + night_shift_timer_start(app); + } + // --- NIGHT SHIFT END --- + + // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; + if(furi_record_exists(RECORD_GUI)) { + Gui* gui = furi_record_open(RECORD_GUI); + u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); + furi_record_close(RECORD_GUI); + } +} + +static void notification_init_settings(NotificationApp* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app); + + if(storage_sd_status(storage) != FSE_OK) { + FURI_LOG_D(TAG, "SD Card not ready, skipping settings"); + return; + } + + notification_apply_settings(app); +} + +// App +int32_t notification_srv(void* p) { + UNUSED(p); + NotificationApp* app = notification_app_alloc(); + + notification_init_settings(app); + + notification_vibro_off(); + notification_sound_off(); + notification_apply_internal_led_layer(&app->display, 0x00); + notification_apply_internal_led_layer(&app->led[0], 0x00); + notification_apply_internal_led_layer(&app->led[1], 0x00); + notification_apply_internal_led_layer(&app->led[2], 0x00); + + furi_record_create(RECORD_NOTIFICATION, app); + + // --- RGB BACKLIGHT SECTION --- + + //setup local variable + set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); + + // define rainbow_timer and they callback + app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); + + // define night_shift_demo_timer and they callback. + // used for Setting menu to demonstrate night_shift_backlight when user change value + app->night_shift_demo_timer = + furi_timer_alloc(night_shift_demo_timer_callback, FuriTimerTypeOnce, app); + + // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) + if(app->settings.rgb.rgb_backlight_installed) { + if(app->settings.rgb.rainbow_mode > 0) { + rainbow_timer_start(app); } else { - FURI_LOG_E(TAG, "Save failed, %s", storage_file_get_error_desc(file)); + rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); + rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); + rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); + rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); } - - storage_file_close(file); - storage_file_free(file); - furi_record_close(RECORD_STORAGE); - - return fs_result; + // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on + } else { + rgb_backlight_set_led_static_color(2, 0); + rgb_backlight_set_led_static_color(1, 0); + rgb_backlight_set_led_static_color(0, 0); + SK6805_update(); } - static void input_event_callback(const void* value, void* context) { - furi_assert(value); - furi_assert(context); - NotificationApp* app = context; - notification_message(app, &sequence_display_backlight_on); - } + // --- RGB BACKLIGHT SECTION END --- - // App alloc - static NotificationApp* notification_app_alloc(void) { - NotificationApp* app = malloc(sizeof(NotificationApp)); - app->queue = furi_message_queue_alloc(8, sizeof(NotificationAppMessage)); - app->display_timer = furi_timer_alloc(notification_display_timer, FuriTimerTypeOnce, app); + NotificationAppMessage message; + while(1) { + furi_check(furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); - app->settings.speaker_volume = 1.0f; - app->settings.display_brightness = 1.0f; - app->settings.led_brightness = 1.0f; - app->settings.display_off_delay_ms = 30000; - app->settings.vibro_on = true; - - app->display.value[LayerInternal] = 0x00; - app->display.value[LayerNotification] = 0x00; - app->display.index = LayerInternal; - app->display.light = LightBacklight; - - app->led[0].value[LayerInternal] = 0x00; - app->led[0].value[LayerNotification] = 0x00; - app->led[0].index = LayerInternal; - app->led[0].light = LightRed; - - app->led[1].value[LayerInternal] = 0x00; - app->led[1].value[LayerNotification] = 0x00; - app->led[1].index = LayerInternal; - app->led[1].light = LightGreen; - - app->led[2].value[LayerInternal] = 0x00; - app->led[2].value[LayerNotification] = 0x00; - app->led[2].index = LayerInternal; - app->led[2].light = LightBlue; - - app->settings.version = NOTIFICATION_SETTINGS_VERSION; - - // display backlight control - app->event_record = furi_record_open(RECORD_INPUT_EVENTS); - furi_pubsub_subscribe(app->event_record, input_event_callback, app); - notification_message(app, &sequence_display_backlight_on); - - // --- NIGHT SHIFT --- - app->current_night_shift = 1.0f; - app->current_night_shift = 1.0f; - app->settings.night_shift = 1.0f; - app->settings.night_shift_start = 1020; - app->settings.night_shift_end = 300; - app->night_shift_timer = - furi_timer_alloc(night_shift_timer_callback, FuriTimerTypePeriodic, app); - // --- NIGHT SHIFT END --- - - // init working variables - app->rainbow_hue = 1; - app->current_night_shift = 1.0f; - - // init rgb.segings values - app->settings.rgb.rgb_backlight_installed = 0; - app->settings.rgb.led_2_color_index = 0; - app->settings.rgb.led_1_color_index = 0; - app->settings.rgb.led_0_color_index = 0; - app->settings.rgb.rainbow_speed_ms = 100; - app->settings.rgb.rainbow_step = 1; - app->settings.rgb.rainbow_saturation = 255; - app->settings.rgb.rainbow_wide = 50; - - // set inital value, later it will be rewriten by loading settings from file - app->settings.lcd_inversion = false; - - return app; - } - - static void notification_storage_callback(const void* message, void* context) { - furi_assert(context); - NotificationApp* app = context; - const StorageEvent* event = message; - - if(event->type == StorageEventTypeCardMount) { - NotificationAppMessage m = { - .type = LoadSettingsMessage, - }; - - furi_check(furi_message_queue_put(app->queue, &m, FuriWaitForever) == FuriStatusOk); - } - } - - static void notification_apply_settings(NotificationApp * app) { - if(!notification_load_settings(app)) { + switch(message.type) { + case NotificationLayerMessage: + notification_process_notification_message(app, &message); + break; + case InternalLayerMessage: + notification_process_internal_message(app, &message); + break; + case SaveSettingsMessage: notification_save_settings(app); + break; + case LoadSettingsMessage: + notification_load_settings(app); + break; } - notification_apply_lcd_contrast(app); - - // --- NIGHT SHIFT --- - // if night_shift enabled then start timer for controlling current_night_shift multiplicator value depent from current time - if(app->settings.night_shift != 1) { - night_shift_timer_start(app); - } - // --- NIGHT SHIFT END --- - - // check RECORD_GUI is exist (insurance on boot time) then use it to setup lcd inversion mode from loaded settings; - if(furi_record_exists(RECORD_GUI)) { - Gui* gui = furi_record_open(RECORD_GUI); - u8x8_d_st756x_set_inversion(&gui->canvas->fb.u8x8, app->settings.lcd_inversion); - furi_record_close(RECORD_GUI); + if(message.back_event != NULL) { + furi_event_flag_set(message.back_event, NOTIFICATION_EVENT_COMPLETE); } } - static void notification_init_settings(NotificationApp * app) { - Storage* storage = furi_record_open(RECORD_STORAGE); - furi_pubsub_subscribe(storage_get_pubsub(storage), notification_storage_callback, app); - - if(storage_sd_status(storage) != FSE_OK) { - FURI_LOG_D(TAG, "SD Card not ready, skipping settings"); - return; - } - - notification_apply_settings(app); - } - - // App - int32_t notification_srv(void* p) { - UNUSED(p); - NotificationApp* app = notification_app_alloc(); - - notification_init_settings(app); - - notification_vibro_off(); - notification_sound_off(); - notification_apply_internal_led_layer(&app->display, 0x00); - notification_apply_internal_led_layer(&app->led[0], 0x00); - notification_apply_internal_led_layer(&app->led[1], 0x00); - notification_apply_internal_led_layer(&app->led[2], 0x00); - - furi_record_create(RECORD_NOTIFICATION, app); - - // --- RGB BACKLIGHT SECTION --- - - //setup local variable - set_rgb_backlight_installed_variable(app->settings.rgb.rgb_backlight_installed); - - // define rainbow_timer and they callback - app->rainbow_timer = furi_timer_alloc(rainbow_timer_callback, FuriTimerTypePeriodic, app); - - // define night_shift_demo_timer and they callback. - // used for Setting menu to demonstrate night_shift_backlight when user change value - app->night_shift_demo_timer = - furi_timer_alloc(night_shift_demo_timer_callback, FuriTimerTypeOnce, app); - - // if rgb_backlight_installed then start rainbow or set leds colors from saved settings (default index = 0) - if(app->settings.rgb.rgb_backlight_installed) { - if(app->settings.rgb.rainbow_mode > 0) { - rainbow_timer_start(app); - } else { - rgb_backlight_set_led_static_color(2, app->settings.rgb.led_2_color_index); - rgb_backlight_set_led_static_color(1, app->settings.rgb.led_1_color_index); - rgb_backlight_set_led_static_color(0, app->settings.rgb.led_0_color_index); - rgb_backlight_update(app->settings.display_brightness * app->current_night_shift); - } - // if rgb_backlight not installed then set default static orange color(index=0) to all leds (0-2) and force light on - } else { - rgb_backlight_set_led_static_color(2, 0); - rgb_backlight_set_led_static_color(1, 0); - rgb_backlight_set_led_static_color(0, 0); - SK6805_update(); - } - - // --- RGB BACKLIGHT SECTION END --- - - NotificationAppMessage message; - while(1) { - furi_check( - furi_message_queue_get(app->queue, &message, FuriWaitForever) == FuriStatusOk); - - switch(message.type) { - case NotificationLayerMessage: - notification_process_notification_message(app, &message); - break; - case InternalLayerMessage: - notification_process_internal_message(app, &message); - break; - case SaveSettingsMessage: - notification_save_settings(app); - break; - case LoadSettingsMessage: - notification_load_settings(app); - break; - } - - if(message.back_event != NULL) { - furi_event_flag_set(message.back_event, NOTIFICATION_EVENT_COMPLETE); - } - } - - return 0; - } + return 0; +} diff --git a/applications/services/notification/notification_app.h b/applications/services/notification/notification_app.h index 094027d89..111fbd481 100644 --- a/applications/services/notification/notification_app.h +++ b/applications/services/notification/notification_app.h @@ -39,7 +39,7 @@ typedef struct { Light light; } NotificationLedLayer; -#define NOTIFICATION_SETTINGS_VERSION 0x06 +#define NOTIFICATION_SETTINGS_VERSION 0x05 #define NOTIFICATION_SETTINGS_PATH INT_PATH(NOTIFICATION_SETTINGS_FILE_NAME) typedef struct { From f91e33daa6013a85fe21dd6127c0576f4910cb00 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Feb 2026 17:27:30 +0300 Subject: [PATCH 052/202] fix typo [ci skip] --- lib/subghz/protocols/power_smart.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index 78915d2b6..8acf714de 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -267,7 +267,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; @@ -311,7 +311,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; From e242f489b0ce11b9649a4791498b6465de1a7415 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:05:21 +0300 Subject: [PATCH 053/202] fmt --- applications/services/notification/notification_app.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/services/notification/notification_app.c b/applications/services/notification/notification_app.c index 8a62f7ac9..408c9473b 100644 --- a/applications/services/notification/notification_app.c +++ b/applications/services/notification/notification_app.c @@ -761,7 +761,7 @@ static bool notification_load_settings(NotificationApp* app) { // "kostyl" for update old setting to new without change settings version if(app->settings.display_off_delay_ms < 2000) app->settings.display_off_delay_ms = 2000; - + return fs_result; } From 273aa1b807508657ddd20dcd778e79e49630fd47 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:11:44 +0300 Subject: [PATCH 054/202] don't use much bits for 8bit values [ci skip] --- applications/main/subghz/helpers/subghz_txrx.c | 6 +++--- applications/main/subghz/helpers/subghz_txrx.h | 5 ++--- applications/main/subghz/subghz_i.h | 2 +- applications/main/subghz/subghz_last_settings.h | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 46d0a7eef..1af603f40 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -109,10 +109,10 @@ void subghz_txrx_set_preset( } uint8_t* - subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint32_t tx_power) { + subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint8_t tx_power) { #define TX_POWER_OFFSET 7 #define TX_PRESET_POWER_COUNT 11 - const uint32_t tx_power_value[TX_PRESET_POWER_COUNT] = { + const uint8_t tx_power_value[TX_PRESET_POWER_COUNT] = { 0, 0xC0, 0xC5, @@ -708,7 +708,7 @@ const char* subghz_txrx_set_preset_internal( SubGhzTxRx* instance, uint32_t frequency, uint8_t index, - uint32_t tx_power) { + uint8_t tx_power) { furi_assert(instance); //Grab the prset name. diff --git a/applications/main/subghz/helpers/subghz_txrx.h b/applications/main/subghz/helpers/subghz_txrx.h index 259fddfc2..5d1cd8ee4 100644 --- a/applications/main/subghz/helpers/subghz_txrx.h +++ b/applications/main/subghz/helpers/subghz_txrx.h @@ -64,8 +64,7 @@ void subghz_txrx_set_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, uint32_t tx_power); +uint8_t* subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint8_t tx_power); /** * Get name of preset @@ -377,4 +376,4 @@ const char* subghz_txrx_set_preset_internal( SubGhzTxRx* instance, uint32_t frequency, uint8_t index, - uint32_t tx_power); + uint8_t tx_power); diff --git a/applications/main/subghz/subghz_i.h b/applications/main/subghz/subghz_i.h index 01f3ccf2f..578995157 100644 --- a/applications/main/subghz/subghz_i.h +++ b/applications/main/subghz/subghz_i.h @@ -93,7 +93,7 @@ struct SubGhz { uint16_t idx_menu_chosen; SubGhzLoadTypeFile load_type_file; - uint32_t tx_power; + uint8_t tx_power; void* rpc_ctx; }; diff --git a/applications/main/subghz/subghz_last_settings.h b/applications/main/subghz/subghz_last_settings.h index e42ee868d..fcd6e1c1b 100644 --- a/applications/main/subghz/subghz_last_settings.h +++ b/applications/main/subghz/subghz_last_settings.h @@ -26,7 +26,7 @@ typedef struct { bool delete_old_signals; float hopping_threshold; bool leds_and_amp; - uint32_t tx_power; + uint8_t tx_power; } SubGhzLastSettings; SubGhzLastSettings* subghz_last_settings_alloc(void); From e96b38da3665580580b7a30c97ea749a31704697 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:21:44 +0300 Subject: [PATCH 055/202] fmt --- applications/main/subghz/scenes/subghz_scene_transmitter.c | 1 + 1 file changed, 1 insertion(+) diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index ed2f04389..273e72a03 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -38,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); From 48db77f307c2b998a34e99317e7a8add0c9e7d18 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 00:32:20 +0300 Subject: [PATCH 056/202] fix settings [ci skip] --- applications/main/subghz/subghz_last_settings.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/applications/main/subghz/subghz_last_settings.c b/applications/main/subghz/subghz_last_settings.c index a38f39026..d7b2b357e 100644 --- a/applications/main/subghz/subghz_last_settings.c +++ b/applications/main/subghz/subghz_last_settings.c @@ -120,10 +120,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, &instance->tx_power, 1)) { + 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); if(!flipper_format_read_float( fff_data_file, SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD, @@ -227,8 +229,8 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) { file, SUBGHZ_LAST_SETTING_FIELD_DELETE_OLD, &instance->delete_old_signals, 1)) { break; } - if(!flipper_format_write_uint32( - file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &instance->tx_power, 1)) { + uint32_t tx_power = instance->tx_power; + if(!flipper_format_write_uint32(file, SUBGHZ_LAST_SETTING_FIELD_TX_POWER, &tx_power, 1)) { break; } if(!flipper_format_write_float( From 9f9ceee1493f4c857735baf2034d590a4de60146 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:37:42 +0300 Subject: [PATCH 057/202] fix storage settings scene exit via favs --- .../scenes/storage_settings_scene_benchmark.c | 20 +++++++++++++++---- ...storage_settings_scene_benchmark_confirm.c | 8 +++++++- .../storage_settings_scene_factory_reset.c | 8 +++++++- .../storage_settings_scene_format_confirm.c | 8 +++++++- .../storage_settings_scene_formatting.c | 10 ++++++++-- .../storage_settings_scene_internal_info.c | 8 +++++++- .../scenes/storage_settings_scene_sd_info.c | 8 +++++++- .../storage_settings_scene_unmount_confirm.c | 8 +++++++- .../scenes/storage_settings_scene_unmounted.c | 10 ++++++++-- .../storage_settings/storage_settings.c | 3 +++ .../storage_settings/storage_settings.h | 2 ++ 11 files changed, 79 insertions(+), 14 deletions(-) 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..b4f52b879 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) { + storage_settings_scene_benchmark_on_exit(app); + 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) { + storage_settings_scene_benchmark_on_exit(app); + 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..462aa4cee 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) { + storage_settings_scene_benchmark_confirm_on_exit(app); + 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..7fa925487 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) { + storage_settings_scene_factory_reset_on_exit(app); + 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 13b368d3f..2670f1495 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) { + storage_settings_scene_format_confirm_on_exit(app); + 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, StorageSettingsFormatting); 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 6a958610e..0ae18741e 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -69,8 +69,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) { + storage_settings_scene_formatting_on_exit(app); + 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..e3e5ffe96 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) { + storage_settings_scene_internal_info_on_exit(app); + 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 cad3fbfaf..d168894b4 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 @@ -78,7 +78,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) { + storage_settings_scene_sd_info_on_exit(app); + 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..65049c33f 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) { + storage_settings_scene_unmount_confirm_on_exit(app); + 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..5b64adb63 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) { + storage_settings_scene_unmounted_on_exit(app); + 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 07656431c..570ca3411 100644 --- a/applications/settings/storage_settings/storage_settings.c +++ b/applications/settings/storage_settings/storage_settings.c @@ -98,7 +98,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 { From 422f3a576d924bc8e06bcd114123739a4ef10aed Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:37:50 +0300 Subject: [PATCH 058/202] upd changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1272d88d..9fb479013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,12 +19,15 @@ * SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) * SubGHz: KeeLoq **add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps)** - may bypass counter on some receivers! * SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 #958 | by @Dmitry422) +* SubGHz: TX Power setting (PR #960 | by @LeeroysHub) * JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) * Apps: Build tag (**3feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Settings: Storage settings exit scenes properly if used via favourites (fixes issue #951) +* LCD: Backlight settings bug fix (PR #962 | by @Dmitry422) * UI: Various small changes * Desktop: Disable winter holidays anims * OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) From aec6045392fc1c55fc70754777d7dfc855406b6a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 01:38:11 +0300 Subject: [PATCH 059/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb479013..6184d09f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Main changes -- Current API: 87.5 +- Current API: 87.6 * SubGHz: **Cardin S449 protocol full support** (64bit keeloq) (with Add manually, and all button codes) (**use FSK12K modulation to read the remote**) (closes issues #735 #908) (by @xMasterX and @zero-mega (thanks!)) * SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) * SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) From a6ca43e8775351781695a9edb004d580192af200 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Feb 2026 03:11:50 +0300 Subject: [PATCH 060/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6184d09f1..0bf9ac068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**3feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**10feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Settings: Storage settings exit scenes properly if used via favourites (fixes issue #951) * LCD: Backlight settings bug fix (PR #962 | by @Dmitry422) From bc6b0d199ecf402d25f83ebc02e62403571aa368 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 04:25:06 +0300 Subject: [PATCH 061/202] storage settings use scene manager for exit --- .../scenes/storage_settings_scene_benchmark.c | 4 ++-- .../scenes/storage_settings_scene_benchmark_confirm.c | 2 +- .../scenes/storage_settings_scene_factory_reset.c | 2 +- .../scenes/storage_settings_scene_format_confirm.c | 2 +- .../scenes/storage_settings_scene_formatting.c | 2 +- .../scenes/storage_settings_scene_internal_info.c | 2 +- .../storage_settings/scenes/storage_settings_scene_sd_info.c | 2 +- .../scenes/storage_settings_scene_unmount_confirm.c | 2 +- .../scenes/storage_settings_scene_unmounted.c | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) 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 b4f52b879..a4a12caa1 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_benchmark.c @@ -157,7 +157,7 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent switch(event.event) { case DialogExResultCenter: if(app->from_favorites) { - storage_settings_scene_benchmark_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { @@ -169,7 +169,7 @@ bool storage_settings_scene_benchmark_on_event(void* context, SceneManagerEvent } else if(event.type == SceneManagerEventTypeBack) { if(sd_status == FSE_OK) { if(app->from_favorites) { - storage_settings_scene_benchmark_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 462aa4cee..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 @@ -49,7 +49,7 @@ bool storage_settings_scene_benchmark_confirm_on_event(void* context, SceneManag case DialogExResultLeft: case DialogExResultCenter: if(app->from_favorites) { - storage_settings_scene_benchmark_confirm_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 7fa925487..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 @@ -45,7 +45,7 @@ bool storage_settings_scene_factory_reset_on_event(void* context, SceneManagerEv case DialogExResultLeft: scene_manager_set_scene_state(app->scene_manager, StorageSettingsFactoryReset, 0); if(app->from_favorites) { - storage_settings_scene_factory_reset_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 2670f1495..550f9be10 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 @@ -42,7 +42,7 @@ bool storage_settings_scene_format_confirm_on_event(void* context, SceneManagerE case DialogExResultLeft: case DialogExResultCenter: if(app->from_favorites) { - storage_settings_scene_format_confirm_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 0ae18741e..85b1450c7 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_formatting.c @@ -70,7 +70,7 @@ bool storage_settings_scene_formatting_on_event(void* context, SceneManagerEvent switch(event.event) { case DialogExResultLeft: if(app->from_favorites) { - storage_settings_scene_formatting_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 e3e5ffe96..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 @@ -57,7 +57,7 @@ bool storage_settings_scene_internal_info_on_event(void* context, SceneManagerEv switch(event.event) { case DialogExResultLeft: if(app->from_favorites) { - storage_settings_scene_internal_info_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 d168894b4..d47d43447 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 @@ -79,7 +79,7 @@ bool storage_settings_scene_sd_info_on_event(void* context, SceneManagerEvent ev case DialogExResultLeft: case DialogExResultCenter: if(app->from_favorites) { - storage_settings_scene_sd_info_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 65049c33f..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 @@ -47,7 +47,7 @@ bool storage_settings_scene_unmount_confirm_on_event(void* context, SceneManager case DialogExResultCenter: case DialogExResultLeft: if(app->from_favorites) { - storage_settings_scene_unmount_confirm_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { 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 5b64adb63..6a3d98604 100644 --- a/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c +++ b/applications/settings/storage_settings/scenes/storage_settings_scene_unmounted.c @@ -58,7 +58,7 @@ bool storage_settings_scene_unmounted_on_event(void* context, SceneManagerEvent switch(event.event) { case DialogExResultCenter: if(app->from_favorites) { - storage_settings_scene_unmounted_on_exit(app); + scene_manager_stop(app->scene_manager); view_dispatcher_stop(app->view_dispatcher); return true; } else { From 14269286eb6eaa66b53d41ebfe02265bc6fb7c71 Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:04:53 +1100 Subject: [PATCH 062/202] TX Power: Use correct Offset and Values for FM PA table. --- .../main/subghz/helpers/subghz_txrx.c | 50 +++++++++++++------ .../scenes/subghz_scene_radio_settings.c | 6 +-- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 46d0a7eef..93f197399 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -110,25 +110,45 @@ void subghz_txrx_set_preset( uint8_t* subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint32_t tx_power) { -#define TX_POWER_OFFSET 7 -#define TX_PRESET_POWER_COUNT 11 - const uint32_t tx_power_value[TX_PRESET_POWER_COUNT] = { +#define TX_POWER_OFFSET_FM 8 +#define TX_POWER_OFFSET_AM 7 +#define TX_PRESET_POWER_COUNT 9 + //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. + const uint8_t tx_power_value_AM[TX_PRESET_POWER_COUNT] = { 0, - 0xC0, - 0xC5, - 0xCD, - 0x86, - 0x50, - 0x37, - 0x26, - 0x1D, - 0x17, - 0x03, + 0xC0, //12dBm + 0xCD, //7dBm + 0x86, //5dBm + 0x50, //0dBm + 0x26, // -10dBm + 0x1D, // -15dBm + 0x17, //-20dBm + 0x03 //-30dBm + }; + + //FM PATable Values. We have 8, not 10 (missing 12dBM and -6dBm that are in AM) + const uint8_t tx_power_value_FM[TX_PRESET_POWER_COUNT] = { + 0, + 0xC0, // 10dBm + 0xC8, //7dBm + 0x84, //5dBm + 0x60, //0dBm + 0x34, //-10dBm + 0x1D, //-15dBm + 0x0E, // -20dBm + 0x12, //-30dBm }; //Set the TX Power Here in the CC1101 register... - if(tx_power) - preset_data[preset_data_size - TX_POWER_OFFSET] = (uint8_t)tx_power_value[tx_power]; + if(tx_power) { + //Are we on an AM or FM preset? The PA table is different! + if(preset_data[preset_data_size - TX_POWER_OFFSET_FM]) { + preset_data[preset_data_size - TX_POWER_OFFSET_FM] = tx_power_value_FM[tx_power]; + } else if(preset_data[preset_data_size - TX_POWER_OFFSET_AM]) { + preset_data[preset_data_size - TX_POWER_OFFSET_AM] = tx_power_value_AM[tx_power]; + } //else //Must be a custom Preset with weird PA table not in FW code, dont touch it. + } //Pass back the preset_so we can call one liners. return preset_data; diff --git a/applications/main/subghz/scenes/subghz_scene_radio_settings.c b/applications/main/subghz/scenes/subghz_scene_radio_settings.c index 87e2d8269..be75723be 100644 --- a/applications/main/subghz/scenes/subghz_scene_radio_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_radio_settings.c @@ -67,15 +67,13 @@ const int32_t debug_counter_val[DEBUG_COUNTER_COUNT] = { }; //TX Power -#define TX_POWER_COUNT 11 +#define TX_POWER_COUNT 9 const char* const tx_power_text[TX_POWER_COUNT] = { "Preset", - "12dBm", - "10dBm", + "10dBm +", "7dBm", "5dBm", "0dBm", - "-6dBm", "-10dBm", "-15dBm", "-20dBm", From 808774c05f83c14ccfc1469fcc17f5ba966f0c7f Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 12 Feb 2026 16:55:19 +0700 Subject: [PATCH 063/202] BT on/off to desktop_lock_menu --- applications/services/bt/bt_service/bt_api.c | 3 +-- applications/services/bt/bt_service/bt_api.h | 5 +++++ .../desktop/scenes/desktop_scene_lock_menu.c | 21 +++++++++++++++---- .../services/desktop/views/desktop_events.h | 3 ++- .../desktop/views/desktop_view_lock_menu.c | 17 ++++++++++----- 5 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 applications/services/bt/bt_service/bt_api.h diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 39b9a099d..8b42d7920 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -1,5 +1,4 @@ -#include "bt_i.h" -#include +#include "bt_api.h" FuriHalBleProfileBase* bt_profile_start( Bt* bt, diff --git a/applications/services/bt/bt_service/bt_api.h b/applications/services/bt/bt_service/bt_api.h new file mode 100644 index 000000000..5120ccc7f --- /dev/null +++ b/applications/services/bt/bt_service/bt_api.h @@ -0,0 +1,5 @@ +#pragma once +#include "bt_i.h" +#include + +extern void bt_set_settings(Bt* bt, const BtSettings* settings); \ No newline at end of file diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index 5ca95c4c5..b74ca7f48 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -10,6 +10,8 @@ #include "../views/desktop_view_lock_menu.h" #include "desktop_scene.h" +#include "applications/services/bt/bt_service/bt_api.h" + #define TAG "DesktopSceneLock" void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) { @@ -34,6 +36,9 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { Desktop* desktop = (Desktop*)context; bool consumed = false; + Bt* bt = furi_record_open(RECORD_BT); + BtSettings bts = bt->bt_settings; + if(event.type == SceneManagerEventTypeTick) { bool check_pin_changed = scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLockMenu); @@ -42,10 +47,17 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { } } else if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case DesktopLockMenuEventLock: - scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); - desktop_lock(desktop); - consumed = true; + // old use case + // case DesktopLockMenuEventLock: + // scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneLockMenu, 0); + // desktop_lock(desktop); + // consumed = true; + // break; + case DesktopLockMenuEventBt: + bts.enabled = !bts.enabled; + bt_set_settings(bt, &bts); + scene_manager_search_and_switch_to_previous_scene( + desktop->scene_manager, DesktopSceneMain); break; case DesktopLockMenuEventDummyModeOn: desktop_set_dummy_mode_state(desktop, true); @@ -78,4 +90,5 @@ bool desktop_scene_lock_menu_on_event(void* context, SceneManagerEvent event) { void desktop_scene_lock_menu_on_exit(void* context) { UNUSED(context); + furi_record_close(RECORD_BT); } diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index b89ef5158..724761d48 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -37,7 +37,8 @@ typedef enum { DesktopDebugEventToggleDebugMode, DesktopDebugEventExit, - DesktopLockMenuEventLock, + //DesktopLockMenuEventLock, + DesktopLockMenuEventBt, DesktopLockMenuEventDummyModeOn, DesktopLockMenuEventDummyModeOff, DesktopLockMenuEventStealthModeOn, diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 1b6bdab9c..fefb7b1bd 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -6,7 +6,8 @@ #include "desktop_view_lock_menu.h" typedef enum { - DesktopLockMenuIndexLock, + //DesktopLockMenuIndexLock, + DesktopLockMenuIndexBt, DesktopLockMenuIndexStealth, DesktopLockMenuIndexDummy, @@ -56,8 +57,9 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { for(size_t i = 0; i < DesktopLockMenuIndexTotalCount; ++i) { const char* str = NULL; - if(i == DesktopLockMenuIndexLock) { - str = "Lock"; + //if(i == DesktopLockMenuIndexLock) { + if(i == DesktopLockMenuIndexBt) { + str = "Bluetooth On/Off"; } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { str = "Unmute"; @@ -126,10 +128,15 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { update); if(event->key == InputKeyOk) { - if(idx == DesktopLockMenuIndexLock) { + if(idx == DesktopLockMenuIndexBt) { if(event->type == InputTypeShort) { - lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + lock_menu->callback(DesktopLockMenuEventBt, lock_menu->context); } + // old use case + // } else if(idx == DesktopLockMenuIndexLock) { + // if(event->type == InputTypeShort) { + // lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + // } } else if(idx == DesktopLockMenuIndexStealth) { if((stealth_mode == false) && (event->type == InputTypeShort)) { lock_menu->callback(DesktopLockMenuEventStealthModeOn, lock_menu->context); From a98c610f6dcee25eed7c53800672485d8e9f788b Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Thu, 12 Feb 2026 17:27:28 +0700 Subject: [PATCH 064/202] BT string in desktop_lock_menu naming depent from BT ON|OFF --- .../desktop/scenes/desktop_scene_lock_menu.c | 1 + .../desktop/views/desktop_view_lock_menu.c | 14 +++++++++++++- .../desktop/views/desktop_view_lock_menu.h | 2 ++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/applications/services/desktop/scenes/desktop_scene_lock_menu.c b/applications/services/desktop/scenes/desktop_scene_lock_menu.c index b74ca7f48..369dcc0ef 100644 --- a/applications/services/desktop/scenes/desktop_scene_lock_menu.c +++ b/applications/services/desktop/scenes/desktop_scene_lock_menu.c @@ -27,6 +27,7 @@ void desktop_scene_lock_menu_on_enter(void* context) { desktop_lock_menu_set_dummy_mode_state(desktop->lock_menu, desktop->settings.dummy_mode); desktop_lock_menu_set_stealth_mode_state( desktop->lock_menu, furi_hal_rtc_is_flag_set(FuriHalRtcFlagStealthMode)); + desktop_lock_menu_set_bt_mode_state(desktop->lock_menu, furi_hal_bt_is_active()); desktop_lock_menu_set_idx(desktop->lock_menu, 0); view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdLockMenu); diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index fefb7b1bd..975db7749 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -40,6 +40,14 @@ void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bo true); } +void desktop_lock_menu_set_bt_mode_state(DesktopLockMenuView* lock_menu, bool bt_mode) { + with_view_model( + lock_menu->view, + DesktopLockMenuViewModel * model, + { model->bt_mode = bt_mode; }, + true); +} + void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { furi_assert(idx < DesktopLockMenuIndexTotalCount); with_view_model( @@ -59,7 +67,11 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { //if(i == DesktopLockMenuIndexLock) { if(i == DesktopLockMenuIndexBt) { - str = "Bluetooth On/Off"; + if(m->bt_mode) { + str = "Bluetooth OFF"; + } else { + str = "Bluetooth ON"; + } } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { str = "Unmute"; diff --git a/applications/services/desktop/views/desktop_view_lock_menu.h b/applications/services/desktop/views/desktop_view_lock_menu.h index a44e6b132..b2db7efb2 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.h +++ b/applications/services/desktop/views/desktop_view_lock_menu.h @@ -19,6 +19,7 @@ typedef struct { uint8_t idx; bool dummy_mode; bool stealth_mode; + bool bt_mode; } DesktopLockMenuViewModel; void desktop_lock_menu_set_callback( @@ -29,6 +30,7 @@ void desktop_lock_menu_set_callback( View* desktop_lock_menu_get_view(DesktopLockMenuView* lock_menu); void desktop_lock_menu_set_dummy_mode_state(DesktopLockMenuView* lock_menu, bool dummy_mode); void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bool stealth_mode); +void desktop_lock_menu_set_bt_mode_state(DesktopLockMenuView* lock_menu, bool bt_mode); void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx); DesktopLockMenuView* desktop_lock_menu_alloc(void); void desktop_lock_menu_free(DesktopLockMenuView* lock_menu); From c6421c9fff228f8ea5ca714cc9d48449f092e5fa Mon Sep 17 00:00:00 2001 From: Leeroy <135471162+LeeroysHub@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:42:03 +1100 Subject: [PATCH 065/202] REFACTOR: Tx power fixes (Use 1 array, dont play with Weird Custom Presets) --- .../main/subghz/helpers/subghz_txrx.c | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 93f197399..4c2765196 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -110,12 +110,14 @@ void subghz_txrx_set_preset( uint8_t* subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint32_t tx_power) { -#define TX_POWER_OFFSET_FM 8 -#define TX_POWER_OFFSET_AM 7 -#define TX_PRESET_POWER_COUNT 9 +#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. - const uint8_t tx_power_value_AM[TX_PRESET_POWER_COUNT] = { + const uint8_t tx_pa_table[TX_PATABLE_COUNT] = { 0, 0xC0, //12dBm 0xCD, //7dBm @@ -124,12 +126,7 @@ uint8_t* 0x26, // -10dBm 0x1D, // -15dBm 0x17, //-20dBm - 0x03 //-30dBm - }; - - //FM PATable Values. We have 8, not 10 (missing 12dBM and -6dBm that are in AM) - const uint8_t tx_power_value_FM[TX_PRESET_POWER_COUNT] = { - 0, + 0x03, //-30dBm 0xC0, // 10dBm 0xC8, //7dBm 0x84, //5dBm @@ -142,12 +139,19 @@ uint8_t* //Set the TX Power Here in the CC1101 register... if(tx_power) { - //Are we on an AM or FM preset? The PA table is different! - if(preset_data[preset_data_size - TX_POWER_OFFSET_FM]) { - preset_data[preset_data_size - TX_POWER_OFFSET_FM] = tx_power_value_FM[tx_power]; - } else if(preset_data[preset_data_size - TX_POWER_OFFSET_AM]) { - preset_data[preset_data_size - TX_POWER_OFFSET_AM] = tx_power_value_AM[tx_power]; - } //else //Must be a custom Preset with weird PA table not in FW code, dont touch it. + //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]; + + //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 + preset_data[preset_data_size - PRESET_POWER_OFFSET_FM] = tx_pa_table[tx_power]; + } else if(am_byte & !fm_byte) { + //Use AM Table + preset_data[preset_data_size - PRESET_POWER_OFFSET_AM] = + tx_pa_table[TX_PATABLE_OFFSET_AM + tx_power]; + } } //Pass back the preset_so we can call one liners. From ab8261e1404bf8dee92cdb6c7c42dff33a90e1bf Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:34:37 +0300 Subject: [PATCH 066/202] power exit into settings [ci skip] --- .../scenes/power_settings_scene_power_off.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index 6328a2381..073116ae5 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -27,7 +27,7 @@ void power_settings_scene_power_off_on_enter(void* context) { dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); } - dialog_ex_set_left_button_text(dialog, "Cancel"); + dialog_ex_set_left_button_text(dialog, "Settings"); dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_context(dialog, app); @@ -42,8 +42,7 @@ bool power_settings_scene_power_off_on_event(void* context, SceneManagerEvent ev if(event.type == SceneManagerEventTypeCustom) { if(event.event == DialogExResultLeft) { if(!scene_manager_previous_scene(app->scene_manager)) { - scene_manager_stop(app->scene_manager); - view_dispatcher_stop(app->view_dispatcher); + scene_manager_next_scene(app->scene_manager, PowerSettingsAppSceneStart); } } else if(event.event == DialogExResultRight) { power_off(app->power); From 3f0f90e0a376db8123a827a9c6f07134b59380c9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 18:50:03 +0300 Subject: [PATCH 067/202] formatting, text fixes --- applications/services/bt/bt_service/bt_api.c | 2 +- applications/services/bt/bt_service/bt_api.h | 2 +- .../desktop/views/desktop_view_lock_menu.c | 19 ++++++++----------- .../scenes/power_settings_scene_power_off.c | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/applications/services/bt/bt_service/bt_api.c b/applications/services/bt/bt_service/bt_api.c index 8b42d7920..861569d71 100644 --- a/applications/services/bt/bt_service/bt_api.c +++ b/applications/services/bt/bt_service/bt_api.c @@ -1,4 +1,4 @@ -#include "bt_api.h" +#include "bt_api.h" FuriHalBleProfileBase* bt_profile_start( Bt* bt, diff --git a/applications/services/bt/bt_service/bt_api.h b/applications/services/bt/bt_service/bt_api.h index 5120ccc7f..24801d13b 100644 --- a/applications/services/bt/bt_service/bt_api.h +++ b/applications/services/bt/bt_service/bt_api.h @@ -2,4 +2,4 @@ #include "bt_i.h" #include -extern void bt_set_settings(Bt* bt, const BtSettings* settings); \ No newline at end of file +extern void bt_set_settings(Bt* bt, const BtSettings* settings); diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 975db7749..995a47f95 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -42,10 +42,7 @@ void desktop_lock_menu_set_stealth_mode_state(DesktopLockMenuView* lock_menu, bo void desktop_lock_menu_set_bt_mode_state(DesktopLockMenuView* lock_menu, bool bt_mode) { with_view_model( - lock_menu->view, - DesktopLockMenuViewModel * model, - { model->bt_mode = bt_mode; }, - true); + lock_menu->view, DesktopLockMenuViewModel * model, { model->bt_mode = bt_mode; }, true); } void desktop_lock_menu_set_idx(DesktopLockMenuView* lock_menu, uint8_t idx) { @@ -68,9 +65,9 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { //if(i == DesktopLockMenuIndexLock) { if(i == DesktopLockMenuIndexBt) { if(m->bt_mode) { - str = "Bluetooth OFF"; + str = "Bluetooth Off"; } else { - str = "Bluetooth ON"; + str = "Bluetooth On"; } } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { @@ -144,11 +141,11 @@ bool desktop_lock_menu_input_callback(InputEvent* event, void* context) { if(event->type == InputTypeShort) { lock_menu->callback(DesktopLockMenuEventBt, lock_menu->context); } - // old use case - // } else if(idx == DesktopLockMenuIndexLock) { - // if(event->type == InputTypeShort) { - // lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); - // } + // old use case + // } else if(idx == DesktopLockMenuIndexLock) { + // if(event->type == InputTypeShort) { + // lock_menu->callback(DesktopLockMenuEventLock, lock_menu->context); + // } } else if(idx == DesktopLockMenuIndexStealth) { if((stealth_mode == false) && (event->type == InputTypeShort)) { lock_menu->callback(DesktopLockMenuEventStealthModeOn, lock_menu->context); diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index 073116ae5..b89c2d0b8 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -25,7 +25,7 @@ void power_settings_scene_power_off_on_enter(void* context) { if(!settings.happy_mode) { dialog_ex_set_text( dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); - dialog_ex_set_icon(dialog, 14, 10, &I_dolph_cry_49x54); + dialog_ex_set_icon(dialog, 24, 10, &I_dolph_cry_49x54); } dialog_ex_set_left_button_text(dialog, "Settings"); dialog_ex_set_right_button_text(dialog, "Power Off"); From 9eb8e475ccb46aa28e193d25a325401bf5b0d68c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:24:32 +0300 Subject: [PATCH 068/202] fix switching, etc. --- .../main/subghz/helpers/subghz_txrx.c | 35 ++++++++++++------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index cf066acbf..9e972ad40 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -109,7 +109,7 @@ void subghz_txrx_set_preset( } uint8_t* - subghz_txrx_set_tx_power(uint8_t* preset_data, size_t preset_data_size, uint32_t tx_power) { + 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 @@ -137,20 +137,29 @@ uint8_t* 0x12, //-30dBm }; - //Set the TX Power Here in the CC1101 register... - if(tx_power) { - //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]; + //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]; - //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 - preset_data[preset_data_size - PRESET_POWER_OFFSET_FM] = tx_pa_table[tx_power]; - } else if(am_byte & !fm_byte) { - //Use AM Table - 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 } } From 0032c55a39ca402f7b1ecbac4b52275298d2b8ef Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:55:47 +0300 Subject: [PATCH 069/202] upd changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bf9ac068..0e2adab8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,11 +20,13 @@ * SubGHz: KeeLoq **add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps)** - may bypass counter on some receivers! * SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 #958 | by @Dmitry422) * SubGHz: TX Power setting (PR #960 | by @LeeroysHub) +* Desktop: BT ON/OFF instead of Lock/Unlock action in desktop lock menu (To lock - hold Up button) (PR #965 | by @Dmitry422) (idea by Dezederix) +* Desktop: Holding back button to power off menu now allows to open power settings on left button (idea by Dezederix) * JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) * NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) * NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) * Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**10feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**12feb2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Settings: Storage settings exit scenes properly if used via favourites (fixes issue #951) * LCD: Backlight settings bug fix (PR #962 | by @Dmitry422) From 75d426382258512e30440a723b88f583662bbdb2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 15 Feb 2026 16:39:58 +0300 Subject: [PATCH 070/202] fix endless tx state at receiver info --- applications/main/subghz/helpers/subghz_types.h | 1 + .../main/subghz/scenes/subghz_scene_receiver_info.c | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_types.h b/applications/main/subghz/helpers/subghz_types.h index 6fbdc9e33..790d00557 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/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 61502c16a..1d1f827f6 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -153,7 +153,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) // 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 = SubGhzNotificationStateIDLE; + subghz->state_notifications = SubGhzNotificationStateTxWait; subghz_block_generic_global.endless_tx = false; return true; @@ -192,7 +192,7 @@ 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 SubGhzNotificationStateIDLE: + 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); @@ -202,6 +202,8 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event) 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); From 9700c98c38cf7fe103dc195f67a971f23c5c29e6 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 15 Feb 2026 22:35:47 +0700 Subject: [PATCH 071/202] Small refactor Signal Settings for best user expirience. Save and apply counter mode immidiatly after changing, without exit from signal_settings menu. --- .../scenes/subghz_scene_signal_settings.c | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 90f99e50c..9b582d2ee 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -65,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) { @@ -311,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); From fbf03ecf3814b00422e2066f18825d4e383b74f5 Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Sun, 15 Feb 2026 23:04:15 +0700 Subject: [PATCH 072/202] Start. Special case. Bypass counter_modes if we just change signal counter. --- lib/subghz/protocols/alutech_at_4n.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 186d5fd25..3723c7ff9 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -293,9 +293,11 @@ 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 in SignalSettings menu then we must passthru counter_modes, just gen and save signal file. + if((alutech_at4n_counter_mode == 0) || subghz_block_generic_global.cnt_need_override) { // Check for OFEX (overflow experimental) mode - if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) { + if((furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) || + subghz_block_generic_global.cnt_need_override) { // 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 From 927c563efa9a36a3f15eea311a86d6b26c12cc7c Mon Sep 17 00:00:00 2001 From: Dmitry422 Date: Mon, 16 Feb 2026 22:19:11 +0700 Subject: [PATCH 073/202] finish --- lib/subghz/protocols/alutech_at_4n.c | 17 ++++++++++++----- lib/subghz/protocols/came_atomo.c | 13 +++++++++++-- lib/subghz/protocols/keeloq.c | 16 +++++++++++++--- lib/subghz/protocols/nice_flor_s.c | 16 +++++++++++++--- lib/subghz/protocols/phoenix_v2.c | 15 ++++++++++++--- 5 files changed, 61 insertions(+), 16 deletions(-) diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 3723c7ff9..959e3dbdb 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, @@ -293,11 +296,13 @@ static bool subghz_protocol_alutech_at_4n_gen_data( instance->generic.serial = (uint32_t)(data >> 24) & 0xFFFFFFFF; } - // if we change counter in SignalSettings menu then we must passthru counter_modes, just gen and save signal file. - if((alutech_at4n_counter_mode == 0) || subghz_block_generic_global.cnt_need_override) { + // 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) || - subghz_block_generic_global.cnt_need_override) { + 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,8 +408,10 @@ 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)) + 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)) { diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index f46b90b2d..bd21dafd1 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, @@ -187,9 +190,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 diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index f47f53492..0ed600ece 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -15,6 +15,9 @@ #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, @@ -241,9 +244,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 @@ -601,7 +605,13 @@ static bool instance->encoder.size_upload = 0; size_t upindex = 0; - if(keeloq_counter_mode == 7) { + // 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--) { diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index b554deccf..5b301573e 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, @@ -148,8 +151,10 @@ 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)) + 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) { @@ -157,9 +162,14 @@ static void subghz_protocol_encoder_nice_flor_s_get_upload( } 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 diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 2dc199795..41f4e1c7d 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, @@ -256,13 +259,19 @@ static bool 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)) + 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 - if(v2_phoenix_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(v2_phoenix_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 From 937e937d3229906cc339e0641870aac36c28c916 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 16 Feb 2026 23:15:07 +0300 Subject: [PATCH 074/202] update mfkey to 4.1 [ci skip] --- applications/system/mfkey/application.fam | 5 +- applications/system/mfkey/crypto1.c | 1 - applications/system/mfkey/crypto1.h | 136 +++- applications/system/mfkey/mfkey.c | 513 +------------ applications/system/mfkey/mfkey.h | 7 +- applications/system/mfkey/mfkey_attack.c | 287 +++++++ applications/system/mfkey/mfkey_attack.h | 21 + .../system/mfkey/mfkey_batch_prelude.c | 707 ++++++++++++++++++ .../system/mfkey/mfkey_batch_prelude.h | 12 + applications/system/mfkey/mfkey_bs_verify.c | 530 +++++++++++++ applications/system/mfkey/mfkey_bs_verify.h | 71 ++ applications/system/mfkey/mfkey_dedup.h | 141 ++++ applications/system/mfkey/mfkey_recovery.c | 288 +++++++ applications/system/mfkey/mfkey_recovery.h | 31 + .../system/mfkey/mfkey_state_expansion.c | 152 ++++ .../system/mfkey/mfkey_state_expansion.h | 17 + 16 files changed, 2390 insertions(+), 529 deletions(-) create mode 100644 applications/system/mfkey/mfkey_attack.c create mode 100644 applications/system/mfkey/mfkey_attack.h create mode 100644 applications/system/mfkey/mfkey_batch_prelude.c create mode 100644 applications/system/mfkey/mfkey_batch_prelude.h create mode 100644 applications/system/mfkey/mfkey_bs_verify.c create mode 100644 applications/system/mfkey/mfkey_bs_verify.h create mode 100644 applications/system/mfkey/mfkey_dedup.h create mode 100644 applications/system/mfkey/mfkey_recovery.c create mode 100644 applications/system/mfkey/mfkey_recovery.h create mode 100644 applications/system/mfkey/mfkey_state_expansion.c create mode 100644 applications/system/mfkey/mfkey_state_expansion.h diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam index 5ece9e777..ec5b730b2 100644 --- a/applications/system/mfkey/application.fam +++ b/applications/system/mfkey/application.fam @@ -8,14 +8,13 @@ App( "gui", "storage", ], - stack_size=1 * 1024, + stack_size=8 * 1024, fap_icon="mfkey.png", fap_category="NFC", fap_author="@noproto", fap_icon_assets="images", - fap_weburl="https://github.com/noproto/FlipperMfkey", fap_description="MIFARE Classic key recovery tool", - fap_version="4.0", + fap_version="4.1", ) App( diff --git a/applications/system/mfkey/crypto1.c b/applications/system/mfkey/crypto1.c index e862b14d1..78a400a96 100644 --- a/applications/system/mfkey/crypto1.c +++ b/applications/system/mfkey/crypto1.c @@ -1,5 +1,4 @@ #pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") #include #include "crypto1.h" diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h index 25205ed70..405a37f9b 100644 --- a/applications/system/mfkey/crypto1.h +++ b/applications/system/mfkey/crypto1.h @@ -2,14 +2,22 @@ #define CRYPTO1_H #include + #include "mfkey.h" #include #include #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) + +// Precomputed mask constants for extend_table +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) + +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAPENDIAN(x) \ ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) @@ -31,7 +39,7 @@ static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); -static const uint8_t lookup1[256] = { +static const uint8_t __attribute__((aligned(4))) lookup1[256] = { 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, @@ -43,7 +51,7 @@ static const uint8_t lookup1[256] = { 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; -static const uint8_t lookup2[256] = { +static const uint8_t __attribute__((aligned(4))) lookup2[256] = { 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, @@ -54,29 +62,104 @@ static const uint8_t lookup2[256] = { 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; -static inline int filter(uint32_t const x) { +static inline __attribute__((always_inline)) int filter(uint32_t const x) { uint32_t f; f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; f |= 0x0d938 >> (x >> 16 & 0xf) & 1; return BIT(0xEC57E80A, f); } -#ifdef __ARM_ARCH_7EM__ -static inline uint8_t evenparity32(uint32_t x) { - // fold 32 bits -> 16 -> 8 -> 4 +// Optimized: compute filter(v) and filter(v|1) with shared work +// Returns packed (f1 << 1 | f0) to avoid pointer overhead +static inline __attribute__((always_inline)) uint32_t filter_pair(uint32_t v) { + // Shared indices (v|1 only changes bit 0) + uint32_t idx_hi = (v >> 8) & 0xff; + uint32_t idx_nib = (v >> 16) & 0xf; + + // Shared lookups and computation + uint8_t l2 = lookup2[idx_hi]; + uint32_t nib_bit = (0x0d938 >> idx_nib) & 1; + uint32_t shared = l2 | nib_bit; + + // lookup1 differs only in bit 0 + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + + // Compute both filters + int f0 = BIT(0xEC57E80A, l1_0 | shared); + int f1 = BIT(0xEC57E80A, l1_1 | shared); + + // Pack: bit 0 = f0, bit 1 = f1 + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Compute filter pair using pre-XOR'd filter constant +// adj_filter = 0xEC57E80A ^ (-(uint32_t)BIT(xks, round)) +// Returns f0/f1 already XOR'd with xks_bit, eliminating xks_bit as a variable +static inline __attribute__((always_inline)) uint32_t + filter_pair_xor(uint32_t v, uint32_t adj_filter) { + uint32_t idx_hi = (v >> 8) & 0xff; + uint32_t idx_nib = (v >> 16) & 0xf; + uint8_t l2 = lookup2[idx_hi]; + uint32_t nib_bit = (0x0d938 >> idx_nib) & 1; + uint32_t shared = l2 | nib_bit; + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + int f0 = BIT(adj_filter, l1_0 | shared); + int f1 = BIT(adj_filter, l1_1 | shared); + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Unpack macros for filter_pair result +#define FILTER_F0(fp) ((fp) & 1) +#define FILTER_F1(fp) (((fp) >> 1) & 1) + +// Optimized filter_pair with precomputed nibble bit +// nib_bit must be 0 or 1 (the result of (0x0d938 >> nibble) & 1) +static inline __attribute__((always_inline)) uint32_t + filter_pair_with_nib(uint32_t v, uint32_t nib_bit) { + uint32_t idx_hi = (v >> 8) & 0xff; + + uint8_t l2 = lookup2[idx_hi]; + uint32_t shared = l2 | nib_bit; // nib_bit precomputed, no shift/mask needed + + uint32_t idx0_lo = v & 0xff; + uint8_t l1_0 = lookup1[idx0_lo]; + uint8_t l1_1 = lookup1[idx0_lo | 1]; + + int f0 = BIT(0xEC57E80A, l1_0 | shared); + int f1 = BIT(0xEC57E80A, l1_1 | shared); + + return (uint32_t)f0 | ((uint32_t)f1 << 1); +} + +// Register-based parity update (avoids array access overhead) +// Calculates new state with parity bits without array/pointer access +static inline __attribute__((always_inline)) uint32_t + update_contribution_reg(uint32_t v, int mask1, int mask2) { + uint32_t p = v >> 25; + p = (p << 1) | evenparity32(v & mask1); + p = (p << 1) | evenparity32(v & mask2); + return (p << 24) | (v & 0xffffff); +} + +static inline __attribute__((always_inline)) uint8_t evenparity32(uint32_t x) { x ^= x >> 16; x ^= x >> 8; x ^= x >> 4; - // magic 0x6996: bit i tells you parity of i (0 ≤ i < 16) - return (uint8_t)((0x6996u >> (x & 0xF)) & 1); + x ^= x >> 2; + x ^= x >> 1; + return (uint8_t)(x & 1); } -#endif -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; +static inline __attribute__((always_inline)) void + update_contribution(unsigned int data[], int item, int mask1, int mask2) { + unsigned int p = data[item] >> 25; // Use unsigned to avoid UB on left shift p = p << 1 | evenparity32(data[item] & mask1); p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); + data[item] = (p << 24) | (data[item] & 0xffffff); } static inline uint32_t crypt_word(struct Crypto1State* s) { @@ -187,29 +270,6 @@ static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int return; } -// TODO: -/* -uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { - uint32_t res_ret = 0; - uint8_t ret; - uint32_t feedin, t, next_in; - for (int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - res_ret |= (ret << (24 ^ i)); - } - return res_ret; -} -*/ - uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { int out; uint8_t ret; @@ -231,7 +291,7 @@ uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { int i; uint32_t ret = 0; for(i = 31; i >= 0; --i) - ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + ret |= (uint32_t)napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); return ret; } diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c index db700be32..97ccbb8c8 100644 --- a/applications/system/mfkey/mfkey.c +++ b/applications/system/mfkey/mfkey.c @@ -1,5 +1,4 @@ #pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") // TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? // (a cache for key_already_found_for_nonce_in_dict) @@ -18,14 +17,17 @@ #include #include "mfkey_icons.h" #include +#include #include #include #include #include #include +#include #include #include "mfkey.h" #include "crypto1.h" +#include "mfkey_attack.h" #include "plugin_interface.h" #include #include @@ -42,12 +44,13 @@ #define LF_POLY_ODD (0x29CE5C) #define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n) & 1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) + +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) #define SWAP(a, b) \ do { \ unsigned int t = a; \ @@ -59,18 +62,18 @@ // #define SIZEOF(arr) sizeof(arr) / sizeof(*arr) // Reduced to 16-bit as these values are small and don't need 32-bit -static int16_t eta_round_time = 44; -static int16_t eta_total_time = 705; +static int16_t eta_round_time = 30; +static int16_t eta_total_time = 481; // MSB_LIMIT: Chunk size (out of 256) - can be 8-bit as it's a small value -static uint8_t MSB_LIMIT = 16; +// Not static - referenced by mfkey_attack.c +uint8_t MSB_LIMIT = 16; -static inline void flush_key_buffer(ProgramState* program_state) { +// Not static - referenced by mfkey_attack.c for static_encrypted attacks +void flush_key_buffer(ProgramState* program_state) { if(program_state->key_buffer && program_state->key_buffer_count > 0 && program_state->cuid_dict) { // Pre-allocate exact size needed: 2 hex chars (key_idx) + 12 hex chars (key) + 1 newline per key size_t total_size = program_state->key_buffer_count * 15; - //FURI_LOG_I(TAG, "Flushing key buffer: %d keys", program_state->key_buffer_count); - //FURI_LOG_I(TAG, "Total size: %d bytes", total_size); char* batch_buffer = malloc(total_size + 1); // +1 for null terminator char* ptr = batch_buffer; @@ -109,323 +112,8 @@ static inline void flush_key_buffer(ProgramState* program_state) { } } -static inline int - check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { - if(!(t->odd | t->even)) return 0; - if(n->attack == mfkey32) { - uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); - if(rb != n->ar0_enc) { - return 0; - } - rollback_word_noret(t, n->nr0_enc, 1); - rollback_word_noret(t, n->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, n->uid_xor_nt1, 0); - crypt_word_noret(t, n->nr1_enc, 1); - if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_nested) { - struct Crypto1State temp = {t->odd, t->even}; - rollback_word_noret(t, n->uid_xor_nt1, 0); - if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { - rollback_word_noret(&temp, n->uid_xor_nt1, 0); - crypto1_get_lfsr(&temp, &(n->key)); - return 1; - } - } else if(n->attack == static_encrypted) { - // TODO: Parity bits from rollback_word? - if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { - // Reduce with parity - uint8_t local_parity_keystream_bits; - struct Crypto1State temp = {t->odd, t->even}; - if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == - n->ks1_1_enc) && - (local_parity_keystream_bits == n->par_1)) { - // Found key candidate - crypto1_get_lfsr(t, &(n->key)); - program_state->num_candidates++; - - // Use key buffer - buffer is guaranteed to be available for static_encrypted - program_state->key_buffer[program_state->key_buffer_count] = n->key; - program_state->key_idx_buffer[program_state->key_buffer_count] = n->key_idx; - program_state->key_buffer_count++; - - // Flush buffer when full - if(program_state->key_buffer_count >= program_state->key_buffer_size) { - flush_key_buffer(program_state); - } - } - } - } - return 0; -} - -static inline __attribute__((hot)) int - state_loop(unsigned int* states_buffer, int xks, int m1, int m2, unsigned int in, int and_val) { - int states_tail = 0; - int xks_bit = 0, round_in = 0; - - // Unroll first 4 rounds (no round_in calculations needed) - // Hoist the filter() calls to just one iteration and reuse the results - // This avoids redundant calculations and improves performance and gives us 2000b of extra ram (11496b free on run) - // V.28/04. Aprox 3s speedup per round. Total 3 keys 7mins 17s!! - for(int round = 1; round <= 4; ++round) { - xks_bit = BIT(xks, round); - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v); - int f1 = filter(v | 1); - - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Round 5 (unrolled) - { - xks_bit = BIT(xks, 5); - int r5_in = ((in >> 2) & and_val) << 24; // 2*(5-4)=2 - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r5_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r5_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Round 6 (unrolled) - { - xks_bit = BIT(xks, 6); - int r6_in = ((in >> 4) & and_val) << 24; // 2*(6-4)=4 - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= r6_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= r6_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - // Loop rounds 7–12 - for(int round = 7; round <= 12; ++round) { - xks_bit = BIT(xks, round); - round_in = ((in >> (2 * (round - 4))) & and_val) << 24; - for(int s = 0; s <= states_tail; ++s) { - unsigned int v = states_buffer[s] << 1; - states_buffer[s] = v; - int f0 = filter(v), f1 = filter(v | 1); - if(__builtin_expect((f0 ^ f1) != 0, 0)) { - states_buffer[s] |= f0 ^ xks_bit; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else if(__builtin_expect(f0 == xks_bit, 1)) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = v | 1; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s++] ^= round_in; - update_contribution(states_buffer, s, m1, m2); - states_buffer[s] ^= round_in; - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - return states_tail; -} - -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; -} - -void quicksort(unsigned int array[], int low, int high) { - // Use insertion sort for small arrays (threshold determined by testing) - if(high - low < 16) { - // Insertion sort - for(int i = low + 1; i <= high; i++) { - unsigned int key = array[i]; - int j = i - 1; - while(j >= low && array[j] > key) { - array[j + 1] = array[j]; - j--; - } - array[j + 1] = key; - } - return; - } - - if(low >= high) return; - - // Median-of-three pivot selection - int middle = low + (high - low) / 2; - if(array[middle] < array[low]) SWAP(array[middle], array[low]); - if(array[high] < array[low]) SWAP(array[high], array[low]); - if(array[high] < array[middle]) SWAP(array[high], array[middle]); - - unsigned int pivot = array[middle]; - - // Rest of quicksort with improved partitioning - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) - i++; - while(array[j] > pivot) - j--; - if(i <= j) { - // swap - unsigned int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - - if(low < j) quicksort(array, low, j); - if(high > i) quicksort(array, i, high); -} - -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { - in <<= 24; - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - data[tbl++] ^= in; - update_contribution(data, tbl, m1, m2); - data[tbl] ^= in; - } else { - data[tbl--] = data[end--]; - } - } - return end; -} - -int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - MfClassicNonce* n, - unsigned int in, - int first_run, - ProgramState* program_state) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, n, program_state)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - in >>= 2; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); - if(o_head > o_tail) return s; - e_tail = extend_table( - even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover( - odd, - o_tail--, - o, - oks, - even, - e_tail--, - e, - eks, - rem, - s, - n, - in, - first_run, - program_state); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; -} - -static inline int sync_state(ProgramState* program_state) { +// Not static - referenced by mfkey_attack.c +int sync_state(ProgramState* program_state) { int ts = furi_hal_rtc_get_timestamp(); int elapsed_time = ts - program_state->eta_timestamp; if(elapsed_time < program_state->eta_round) { @@ -445,148 +133,6 @@ static inline int sync_state(ProgramState* program_state) { return 0; } -int calculate_msb_tables( - int oks, - int eks, - int msb_round, - MfClassicNonce* n, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - unsigned int in, - ProgramState* program_state) { - unsigned int msb_head = (MSB_LIMIT * msb_round); - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0; - int semi_state = 0; - unsigned int msb = 0; - - // Preprocessed in value - in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; - - // Clear MSB arrays once before loop instead of inside loop - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - - // Bit values to check - calculate once outside the loop - int oks_bit = oks & 1; - int eks_bit = eks & 1; - - // Check for stop request less frequently - int sync_check_interval = 32768 * 2; // Doubled the interval - - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % sync_check_interval == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } - - // Process both filter conditions in one pass when possible - int filter_semi_state = filter(semi_state); - - // Check oks condition - if(filter_semi_state == oks_bit) { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); - - for(int i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - // Calculate index once - int msb_idx = msb - msb_head; - - // Avoid sequential scan by using a direct flag - int found = 0; - for(int j = 0; j < odd_msbs[msb_idx].tail; j++) { - if(odd_msbs[msb_idx].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - int tail = odd_msbs[msb_idx].tail++; - odd_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - - // Check eks condition - if(filter_semi_state == eks_bit) { - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); - - for(int i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - // Calculate index once - int msb_idx = msb - msb_head; - - // Avoid sequential scan - int found = 0; - for(int j = 0; j < even_msbs[msb_idx].tail; j++) { - if(even_msbs[msb_idx].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - int tail = even_msbs[msb_idx].tail++; - even_msbs[msb_idx].states[tail] = states_buffer[i]; - } - } - } - } - } - - // Shift once outside the loop - oks >>= 12; - eks >>= 12; - - // Process results - for(int i = 0; i < MSB_LIMIT; i++) { - if((i % 4) == 0 && sync_state(program_state) == 1) { - return 0; - } - - // Only clear buffers if they're going to be used - if(odd_msbs[i].tail > 0 || even_msbs[i].tail > 0) { - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy( - temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - n, - in >> 16, - 1, - program_state); - - if(res == -1) { - return 1; - } - } - } - - return 0; -} - void** allocate_blocks(const size_t* block_sizes, int num_blocks) { void** block_pointers = malloc(num_blocks * sizeof(void*)); if(!block_pointers) { @@ -619,12 +165,15 @@ void** allocate_blocks(const size_t* block_sizes, int num_blocks) { bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { bool found = false; - const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; - const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + // Packed 24-bit Msb format: 16 buckets × 2312 bytes each = 36992 + // states_buffer needs 1024 elements × 4 bytes = 4096 + const size_t block_sizes[] = {36992, 36992, 5120, 5120, 4096}; + // Reduced: 8 buckets × 2312 bytes each = 18496 + const size_t reduced_block_sizes[] = {18496, 18496, 5120, 5120, 4096}; const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); // Reset globals each nonce - eta_round_time = 44; - eta_total_time = 705; + eta_round_time = 30; + eta_total_time = 481; MSB_LIMIT = 16; // Use half speed (reduced block sizes) for static encrypted nonces so we can buffer keys @@ -720,7 +269,7 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ program_state->search = msb; program_state->eta_round = eta_round_time; program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( + if(calculate_msb_tables_optimized( oks, eks, msb, @@ -732,8 +281,6 @@ bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_ temp_states_even, in, program_state)) { - // int bench_stop = furi_hal_rtc_get_timestamp(); - // FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); found = true; break; } @@ -810,7 +357,6 @@ void mfkey(ProgramState* program_state) { } uint32_t i = 0, j = 0; - // FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); Storage* storage = furi_record_open(RECORD_STORAGE); FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); @@ -873,7 +419,6 @@ void mfkey(ProgramState* program_state) { // TODO: Already closed? buffered_file_stream_close(nonce_arr->stream); stream_free(nonce_arr->stream); - // FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); program_state->mfkey_state = MFKeyAttack; // TODO: Work backwards on this array and free memory for(i = 0; i < nonce_arr->total_nonces; i++) { @@ -884,7 +429,6 @@ void mfkey(ProgramState* program_state) { (program_state->num_completed)++; continue; } - // FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); FuriString* cuid_dict_path; switch(next_nonce.attack) { case mfkey32: @@ -961,9 +505,7 @@ void mfkey(ProgramState* program_state) { } // TODO: Update display to show all keys were found // TODO: Prepend found key(s) to user dictionary file - // FURI_LOG_I(TAG, "Unique keys found:"); for(i = 0; i < keyarray_size; i++) { - // FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); } if(keyarray_size > 0) { @@ -975,7 +517,6 @@ void mfkey(ProgramState* program_state) { if(program_state->mfkey_state == Error) { return; } - // FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG program_state->mfkey_state = Complete; // No need to alert the user if they asked it to stop if(!(program_state->close_thread_please)) { @@ -1124,7 +665,7 @@ int32_t mfkey_main() { gui_add_view_port(gui, view_port, GuiLayerFullscreen); program_state->mfkeythread = - furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + furi_thread_alloc_ex("MFKeyWorker", 4096, mfkey_worker_thread, program_state); InputEvent input_event; for(bool main_loop = true; main_loop;) { diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h index 0e5ff01dd..c01d3a989 100644 --- a/applications/system/mfkey/mfkey.h +++ b/applications/system/mfkey/mfkey.h @@ -12,9 +12,14 @@ struct Crypto1State { uint32_t odd, even; }; + +#define MSB_BUCKET_CAPACITY 768 + struct Msb { int tail; - uint32_t states[768]; + // Store 24-bit states packed into bytes (MSB is implicit from bucket index). + // CAPACITY * 3 bytes for data + 4 bytes padding for safe unaligned 32-bit write. + uint8_t states[MSB_BUCKET_CAPACITY * 3 + 4]; }; typedef enum { diff --git a/applications/system/mfkey/mfkey_attack.c b/applications/system/mfkey/mfkey_attack.c new file mode 100644 index 000000000..2fd1257f3 --- /dev/null +++ b/applications/system/mfkey/mfkey_attack.c @@ -0,0 +1,287 @@ +#pragma GCC optimize("O3") + +#include +#include + +#include +#include "mfkey_attack.h" + +#include "crypto1.h" +#include "mfkey_bs_verify.h" +#include "mfkey_dedup.h" +#include "mfkey_state_expansion.h" +#include "mfkey_recovery.h" +#include "mfkey_batch_prelude.h" + +volatile bool g_abort_attack = false; +ProgramState* g_program_state = NULL; // For static_encrypted key buffering + +extern int sync_state(ProgramState* program_state); +extern void flush_key_buffer(ProgramState* program_state); +extern uint8_t MSB_LIMIT; + +#define SWAP(a, b) \ + do { \ + unsigned int t = a; \ + a = b; \ + b = t; \ + } while(0) + +// Forces x into a register, preventing the compiler from hoisting BIT(x, n) +// extractions out of loops and spilling all 16 keystream bits to stack. +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Precomputed Round 4 lane survival masks, indexed by [shared_value][target_bit]. +// Each 16-bit half covers one half-batch (lo/hi). + +static const uint16_t R4_LANE_MASK[8][2] = { + /* shared=0 */ {0xFFFF, 0x26C7}, + /* shared=1 */ {0xD938, 0x26C7}, + /* shared=2 */ {0xFFFF, 0xFFFF}, + /* shared=3 */ {0x26C7, 0xFFFF}, + /* shared=4 */ {0xFFFF, 0x26C7}, + /* shared=5 */ {0x26C7, 0xD938}, + /* shared=6 */ {0x26C7, 0xFFFF}, + /* shared=7 */ {0x26C7, 0xD938}, +}; + +int calculate_msb_tables_optimized( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + // Set global state for hot-path functions (avoids passing through recursion) + g_program_state = program_state; + g_abort_attack = false; + + unsigned int msb_head = (MSB_LIMIT * msb_round); + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + + int states_tail = 0; + unsigned int msb = 0; + + // Preprocessed in value + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + + // Clear MSB arrays + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + +// Identity mask deduplication +#define DISABLE_IDENTITY_FILTER 0 +#if !DISABLE_IDENTITY_FILTER + // Use the idle temp_states buffers as scratch space for bitmask filters. + // Each filter needs (MSB_LIMIT * 64) = 1024 uint32_t entries = 4096 bytes + // temp_states_odd/even are each 1280 elements, so we split them: + uint32_t* odd_msb_filters = (uint32_t*)temp_states_odd; + uint32_t* even_msb_filters = (uint32_t*)temp_states_even; + memset(temp_states_odd, 0, 1024 * sizeof(unsigned int)); + memset(temp_states_even, 0, 1024 * sizeof(unsigned int)); +#endif + + // Iterate in batches of 32 (batch_base has bits 0-4 = 0) + for(int batch_base = (1 << 20) & ~31; batch_base >= 0; batch_base -= 32) { + // Prevent compiler from hoisting BIT(oks/eks, N) extractions out of loop + OPT_BARRIER(oks); + OPT_BARRIER(eks); + + // Periodic sync check (every 2048 batches = 65536 semi-states) + if((batch_base & 0xFFE0) == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + // R4 Lane Mask: precompute full 32-bit masks for both streams + // shared_lo/hi, BIT(oks,4), BIT(eks,4) are all constant per batch, + // so the entire R4 mask is batch-invariant. Precompute once here to + // avoid 2 Flash table reads + address arithmetic per inner-loop hit. + uint32_t nib_bit_r4 = (0x0d938 >> ((batch_base >> 12) & 0xF)) & 1; + uint32_t l2_base_r4 = (batch_base >> 4) & 0xFE; + uint32_t shared_lo_r4 = lookup2[l2_base_r4] | nib_bit_r4; + uint32_t shared_hi_r4 = lookup2[l2_base_r4 | 1] | nib_bit_r4; + uint32_t r4_mask_oks = (uint32_t)R4_LANE_MASK[shared_lo_r4][BIT(oks, 4)] | + ((uint32_t)R4_LANE_MASK[shared_hi_r4][BIT(oks, 4)] << 16); + uint32_t r4_mask_eks = (uint32_t)R4_LANE_MASK[shared_lo_r4][BIT(eks, 4)] | + ((uint32_t)R4_LANE_MASK[shared_hi_r4][BIT(eks, 4)] << 16); + + // OKS processing + uint32_t oks_leaf_masks[8]; + uint32_t valid_oks = batch_prelude_unified(batch_base, oks, r4_mask_oks, oks_leaf_masks); + + if(valid_oks) { + uint32_t node_base = (batch_base << 3); + uint32_t active = valid_oks; + while(active) { + int lane = __builtin_ctz(active); + active &= active - 1; + + uint32_t lane_bit = 1u << lane; + uint32_t base_state = node_base | (lane << 3); + int count = 0; + for(int c = 0; c < 8; c++) + if(oks_leaf_masks[c] & lane_bit) states_buffer[count++] = base_state | c; + + if(count > 0) { + states_tail = + state_loop_r4(states_buffer, count, oks, CONST_M1_1, CONST_M2_1, 0, 0); + + // Bucket Insertion + for(int i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + int msb_idx = msb - msb_head; + uint32_t state = states_buffer[i]; + +#if DISABLE_IDENTITY_FILTER + if(odd_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + int tail = odd_msbs[msb_idx].tail++; + memcpy(&odd_msbs[msb_idx].states[tail * 3], &state, 3); + } +#else + uint32_t fingerprint = FIB_HASH_20BIT(state); + uint32_t filter_idx = (msb_idx << 6) | (fingerprint >> 5); + uint32_t mask = 1U << (fingerprint & 31); + + bool already_exists = false; + if(odd_msb_filters[filter_idx] & mask) { + already_exists = scan_for_duplicate_8x( + odd_msbs[msb_idx].states, + odd_msbs[msb_idx].tail, + state & 0x00FFFFFF); + } + + if(!already_exists && odd_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + odd_msb_filters[filter_idx] |= mask; + int tail = odd_msbs[msb_idx].tail++; + memcpy(&odd_msbs[msb_idx].states[tail * 3], &state, 3); + } +#endif + } + } + } + } + } + + // EKS processing + uint32_t eks_leaf_masks[8]; + uint32_t valid_eks = batch_prelude_unified(batch_base, eks, r4_mask_eks, eks_leaf_masks); + + if(valid_eks) { + uint32_t node_base = (batch_base << 3); + uint32_t active = valid_eks; + while(active) { + int lane = __builtin_ctz(active); + active &= active - 1; + + uint32_t lane_bit = 1u << lane; + uint32_t base_state = node_base | (lane << 3); + int count = 0; + for(int c = 0; c < 8; c++) + if(eks_leaf_masks[c] & lane_bit) states_buffer[count++] = base_state | c; + + if(count > 0) { + states_tail = + state_loop_r4(states_buffer, count, eks, CONST_M1_2, CONST_M2_2, in, 3); + + // Bucket Insertion + for(int i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + int msb_idx = msb - msb_head; + uint32_t state = states_buffer[i]; + +#if DISABLE_IDENTITY_FILTER + if(even_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + int tail = even_msbs[msb_idx].tail++; + memcpy(&even_msbs[msb_idx].states[tail * 3], &state, 3); + } +#else + uint32_t fingerprint = FIB_HASH_20BIT(state); + uint32_t filter_idx = (msb_idx << 6) | (fingerprint >> 5); + uint32_t mask = 1U << (fingerprint & 31); + + bool already_exists = false; + if(even_msb_filters[filter_idx] & mask) { + already_exists = scan_for_duplicate_8x( + even_msbs[msb_idx].states, + even_msbs[msb_idx].tail, + state & 0x00FFFFFF); + } + + if(!already_exists && even_msbs[msb_idx].tail < MSB_BUCKET_CAPACITY) { + even_msb_filters[filter_idx] |= mask; + int tail = even_msbs[msb_idx].tail++; + memcpy(&even_msbs[msb_idx].states[tail * 3], &state, 3); + } +#endif + } + } + } + } + } + } + + // Shift keystream for old_recover + oks >>= 12; + eks >>= 12; + + // Verification phase + for(int i = 0; i < MSB_LIMIT; i++) { + if((i % 4) == 0) { + if(sync_state(program_state) == 1) { + g_abort_attack = true; + return 0; + } + } + + // Only process MSB buckets with candidates on both sides + if(odd_msbs[i].tail > 0 && even_msbs[i].tail > 0) { + uint32_t current_msb_val = (uint32_t)(msb_head + i) << 24; + + for(int k = 0; k < odd_msbs[i].tail; k++) { + uint32_t raw = 0; + memcpy(&raw, &odd_msbs[i].states[k * 3], 3); + temp_states_odd[k] = raw | current_msb_val; + } + + for(int k = 0; k < even_msbs[i].tail; k++) { + uint32_t raw = 0; + memcpy(&raw, &even_msbs[i].states[k * 3], 3); + temp_states_even[k] = raw | current_msb_val; + } + + // Bitsliced verification for all attack types (mfkey32, static_nested, + // static_encrypted). Each type uses its own 32-way SWAR kernel. + int res = old_recover_bs( + temp_states_odd, + 0, + odd_msbs[i].tail - 1, + oks, + temp_states_even, + 0, + even_msbs[i].tail - 1, + eks, + 3, + 0, + n, + in >> 16, + 1); + + if(res == -1) { + return 1; // Key found + } else if(res == -2) { + return 0; // User aborted + } + } + } + + return 0; +} diff --git a/applications/system/mfkey/mfkey_attack.h b/applications/system/mfkey/mfkey_attack.h new file mode 100644 index 000000000..a6fbf5783 --- /dev/null +++ b/applications/system/mfkey/mfkey_attack.h @@ -0,0 +1,21 @@ +#ifndef MFKEY_ATTACK_H +#define MFKEY_ATTACK_H + +#include "mfkey.h" + +// Main MSB table calculation - runs attack for one MSB round +// Returns 1 if key found, 0 otherwise +int calculate_msb_tables_optimized( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state); + +#endif // MFKEY_ATTACK_H diff --git a/applications/system/mfkey/mfkey_batch_prelude.c b/applications/system/mfkey/mfkey_batch_prelude.c new file mode 100644 index 000000000..63b1290e4 --- /dev/null +++ b/applications/system/mfkey/mfkey_batch_prelude.c @@ -0,0 +1,707 @@ +// MFKey Batch Prelude - 32-lane parallel tree expansion through rounds 0-3 +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_batch_prelude.h" +#include "crypto1.h" +#include "mfkey_dedup.h" +#include + +// Precomputed filter LUTs for shared input values 0..7 +// Derived from 0xEC57E80A stride 8 (lookup2 outputs even values, nibble adds 0 or 1) +static const uint8_t FILTER_LUT_TABLE[8] = {0x4, 0x5, 0xC, 0xB, 0x4, 0xA, 0xE, 0xA}; + +// Super-LUT: Combined bitmasks indexed by full LUT value (0-15) +// Eliminates conditional branches - single indexed load per round +// Total: 2KB (8×16 + 4×2×16 + 2×4×16 + 8×16 = 512 uint32_t) + +// Round 0: 8 offsets × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R0_COMBINED[8][16] = { + { + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000DD3, + 0x0DD30000, + 0x0DD30DD3, + 0x0000F22C, + 0x0000FFFF, + 0x0DD3F22C, + 0x0DD3FFFF, + 0xF22C0000, + 0xF22C0DD3, + 0xFFFF0000, + 0xFFFF0DD3, + 0xF22CF22C, + 0xF22CFFFF, + 0xFFFFF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + 0x00000000, + 0x0DD30DD3, + 0x00000000, + 0x0DD30DD3, + 0xF22CF22C, + 0xFFFFFFFF, + 0xF22CF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30000, + 0x00000DD3, + 0x0DD30DD3, + 0xF22C0000, + 0xFFFF0000, + 0xF22C0DD3, + 0xFFFF0DD3, + 0x0000F22C, + 0x0DD3F22C, + 0x0000FFFF, + 0x0DD3FFFF, + 0xF22CF22C, + 0xFFFFF22C, + 0xF22CFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000DD3, + 0x0DD30000, + 0x0DD30DD3, + 0x0000F22C, + 0x0000FFFF, + 0x0DD3F22C, + 0x0DD3FFFF, + 0xF22C0000, + 0xF22C0DD3, + 0xFFFF0000, + 0xFFFF0DD3, + 0xF22CF22C, + 0xF22CFFFF, + 0xFFFFF22C, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0DD30000, + 0x00000DD3, + 0x0DD30DD3, + 0xF22C0000, + 0xFFFF0000, + 0xF22C0DD3, + 0xFFFF0DD3, + 0x0000F22C, + 0x0DD3F22C, + 0x0000FFFF, + 0x0DD3FFFF, + 0xF22CF22C, + 0xFFFFF22C, + 0xF22CFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0x00000000, + 0x00000000, + 0x0DD30DD3, + 0x0DD30DD3, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xF22CF22C, + 0xF22CF22C, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, +}; + +// Round 1: 4 offsets × 2 children × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R1_COMBINED[4][2][16] = { + { + { + 0x00000000, + 0x003D3D3D, + 0x3D000000, + 0x3D3D3D3D, + 0x00C2C2C2, + 0x00FFFFFF, + 0x3DC2C2C2, + 0x3DFFFFFF, + 0xC2000000, + 0xC23D3D3D, + 0xFF000000, + 0xFF3D3D3D, + 0xC2C2C2C2, + 0xC2FFFFFF, + 0xFFC2C2C2, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00292929, + 0x29000000, + 0x29292929, + 0x00D6D6D6, + 0x00FFFFFF, + 0x29D6D6D6, + 0x29FFFFFF, + 0xD6000000, + 0xD6292929, + 0xFF000000, + 0xFF292929, + 0xD6D6D6D6, + 0xD6FFFFFF, + 0xFFD6D6D6, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x3D3D0000, + 0x00003D3D, + 0x3D3D3D3D, + 0xC2C20000, + 0xFFFF0000, + 0xC2C23D3D, + 0xFFFF3D3D, + 0x0000C2C2, + 0x3D3DC2C2, + 0x0000FFFF, + 0x3D3DFFFF, + 0xC2C2C2C2, + 0xFFFFC2C2, + 0xC2C2FFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x29290000, + 0x00002929, + 0x29292929, + 0xD6D60000, + 0xFFFF0000, + 0xD6D62929, + 0xFFFF2929, + 0x0000D6D6, + 0x2929D6D6, + 0x0000FFFF, + 0x2929FFFF, + 0xD6D6D6D6, + 0xFFFFD6D6, + 0xD6D6FFFF, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x003D3D00, + 0x3D00003D, + 0x3D3D3D3D, + 0x00C2C200, + 0x00FFFF00, + 0x3DC2C23D, + 0x3DFFFF3D, + 0xC20000C2, + 0xC23D3DC2, + 0xFF0000FF, + 0xFF3D3DFF, + 0xC2C2C2C2, + 0xC2FFFFC2, + 0xFFC2C2FF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00292900, + 0x29000029, + 0x29292929, + 0x00D6D600, + 0x00FFFF00, + 0x29D6D629, + 0x29FFFF29, + 0xD60000D6, + 0xD62929D6, + 0xFF0000FF, + 0xFF2929FF, + 0xD6D6D6D6, + 0xD6FFFFD6, + 0xFFD6D6FF, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x00003D00, + 0x3D3D003D, + 0x3D3D3D3D, + 0x0000C200, + 0x0000FF00, + 0x3D3DC23D, + 0x3D3DFF3D, + 0xC2C200C2, + 0xC2C23DC2, + 0xFFFF00FF, + 0xFFFF3DFF, + 0xC2C2C2C2, + 0xC2C2FFC2, + 0xFFFFC2FF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00002900, + 0x29290029, + 0x29292929, + 0x0000D600, + 0x0000FF00, + 0x2929D629, + 0x2929FF29, + 0xD6D600D6, + 0xD6D629D6, + 0xFFFF00FF, + 0xFFFF29FF, + 0xD6D6D6D6, + 0xD6D6FFD6, + 0xFFFFD6FF, + 0xFFFFFFFF, + }, + }, +}; + +// Round 2: 2 offsets × 4 children × 16 LUT values (regenerated from filter() brute-force) +static const uint32_t R2_COMBINED[2][4][16] = { + { + { + 0x00000000, + 0x77000777, + 0x00777000, + 0x77777777, + 0x88000888, + 0xFF000FFF, + 0x88777888, + 0xFF777FFF, + 0x00888000, + 0x77888777, + 0x00FFF000, + 0x77FFF777, + 0x88888888, + 0xFF888FFF, + 0x88FFF888, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x11000111, + 0x00111000, + 0x11111111, + 0xEE000EEE, + 0xFF000FFF, + 0xEE111EEE, + 0xFF111FFF, + 0x00EEE000, + 0x11EEE111, + 0x00FFF000, + 0x11FFF111, + 0xEEEEEEEE, + 0xFFEEEFFF, + 0xEEFFFEEE, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x66000666, + 0x00666000, + 0x66666666, + 0x99000999, + 0xFF000FFF, + 0x99666999, + 0xFF666FFF, + 0x00999000, + 0x66999666, + 0x00FFF000, + 0x66FFF666, + 0x99999999, + 0xFF999FFF, + 0x99FFF999, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x66000666, + 0x00666000, + 0x66666666, + 0x99000999, + 0xFF000FFF, + 0x99666999, + 0xFF666FFF, + 0x00999000, + 0x66999666, + 0x00FFF000, + 0x66FFF666, + 0x99999999, + 0xFF999FFF, + 0x99FFF999, + 0xFFFFFFFF, + }, + }, + { + { + 0x00000000, + 0x00700770, + 0x77077007, + 0x77777777, + 0x00800880, + 0x00F00FF0, + 0x77877887, + 0x77F77FF7, + 0x88088008, + 0x88788778, + 0xFF0FF00F, + 0xFF7FF77F, + 0x88888888, + 0x88F88FF8, + 0xFF8FF88F, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00100110, + 0x11011001, + 0x11111111, + 0x00E00EE0, + 0x00F00FF0, + 0x11E11EE1, + 0x11F11FF1, + 0xEE0EE00E, + 0xEE1EE11E, + 0xFF0FF00F, + 0xFF1FF11F, + 0xEEEEEEEE, + 0xEEFEEFFE, + 0xFFEFFEEF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00600660, + 0x66066006, + 0x66666666, + 0x00900990, + 0x00F00FF0, + 0x66966996, + 0x66F66FF6, + 0x99099009, + 0x99699669, + 0xFF0FF00F, + 0xFF6FF66F, + 0x99999999, + 0x99F99FF9, + 0xFF9FF99F, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00600660, + 0x66066006, + 0x66666666, + 0x00900990, + 0x00F00FF0, + 0x66966996, + 0x66F66FF6, + 0x99099009, + 0x99699669, + 0xFF0FF00F, + 0xFF6FF66F, + 0x99999999, + 0x99F99FF9, + 0xFF9FF99F, + 0xFFFFFFFF, + }, + }, +}; + +// Round 3: Individual child masks for target=1 — [child_idx][lut] +// child_idx c encodes R1-R2-R3 binary choices (b0*4 + b1*2 + b2) +// For target=0: use ~mask at runtime (complement) +// Replaces R3_PAIRED: same total size (512 bytes) +// Verification: R3_PAIRED[1][i][lut] == R3_INDIVIDUAL[2*i][lut] | R3_INDIVIDUAL[2*i+1][lut] +static const uint32_t R3_INDIVIDUAL[8][16] = { + { + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + 0x00000000, + 0x0C3CF03F, + 0xF3C30FC0, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0828A02A, + 0xA2820A80, + 0xAAAAAAAA, + 0x04145015, + 0x0C3CF03F, + 0xA6965A95, + 0xAEBEFABF, + 0x51410540, + 0x5969A56A, + 0xF3C30FC0, + 0xFBEBAFEA, + 0x55555555, + 0x5D7DF57F, + 0xF7D75FD5, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x0828A02A, + 0xA2820A80, + 0xAAAAAAAA, + 0x04145015, + 0x0C3CF03F, + 0xA6965A95, + 0xAEBEFABF, + 0x51410540, + 0x5969A56A, + 0xF3C30FC0, + 0xFBEBAFEA, + 0x55555555, + 0x5D7DF57F, + 0xF7D75FD5, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x0C3CF03F, + 0x0C3CF03F, + 0x0C3CF03F, + 0x0C3CF03F, + 0xF3C30FC0, + 0xF3C30FC0, + 0xF3C30FC0, + 0xF3C30FC0, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, + { + 0x00000000, + 0x04145015, + 0x51410540, + 0x55555555, + 0x0828A02A, + 0x0C3CF03F, + 0x5969A56A, + 0x5D7DF57F, + 0xA2820A80, + 0xA6965A95, + 0xF3C30FC0, + 0xF7D75FD5, + 0xAAAAAAAA, + 0xAEBEFABF, + 0xFBEBAFEA, + 0xFFFFFFFF, + }, +}; + +// Combined prefilter + reconstruction in one pass. +// leaf_masks[c] (c=0..7) gives lanes where child c survives R0-R3+R4. +// Return value is OR of all 8 leaf_masks (= lane survival mask). +uint32_t + batch_prelude_unified(uint32_t batch_base, int oks, uint32_t r4_mask, uint32_t leaf_masks[8]) { + OPT_BARRIER(oks); + + // --- ROUND 0 --- + uint32_t idx_hi = (batch_base >> 8) & 0xFF; + uint32_t idx_nib = (batch_base >> 16) & 0xF; + uint32_t shared = lookup2[idx_hi] | ((0x0d938 >> idx_nib) & 1); + int lut = FILTER_LUT_TABLE[shared]; + uint32_t off0 = (batch_base >> 5) & 7; + + uint32_t valid = R0_COMBINED[off0][lut]; + if((oks & 1) == 0) valid = ~valid; + if(!valid) return 0; + + // --- ROUND 1 --- + int target = BIT(oks, 1); + uint32_t node_base = (batch_base << 1); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + uint32_t off1 = (batch_base >> 5) & 3; + + uint32_t p0 = R1_COMBINED[off1][0][lut]; + uint32_t p1 = R1_COMBINED[off1][1][lut]; + if(target == 0) { + p0 = ~p0; + p1 = ~p1; + } + p0 &= valid; + p1 &= valid; + if(!(p0 | p1)) return 0; + + // --- ROUND 2 --- + target = BIT(oks, 2); + node_base = (batch_base << 2); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + uint32_t off2 = (batch_base >> 5) & 1; + + uint32_t p00 = R2_COMBINED[off2][0][lut]; + uint32_t p01 = R2_COMBINED[off2][1][lut]; + uint32_t p10 = R2_COMBINED[off2][2][lut]; + uint32_t p11 = R2_COMBINED[off2][3][lut]; + if(target == 0) { + p00 = ~p00; + p01 = ~p01; + p10 = ~p10; + p11 = ~p11; + } + p00 &= p0; + p01 &= p0; + p10 &= p1; + p11 &= p1; + if(!(p00 | p01 | p10 | p11)) return 0; + + // --- ROUND 3 (individual child masks) --- + node_base = (batch_base << 3); + shared = lookup2[(node_base >> 8) & 0xFF] | ((0x0d938 >> ((node_base >> 16) & 0xF)) & 1); + lut = FILTER_LUT_TABLE[shared]; + + int target3 = BIT(oks, 3); + + // Load 8 individual child masks, apply target complement, AND with parent + r4_mask + // Child c: parent is p[c>>2][c&2 ? 1 : 0] → parent index from R1 bit (c>>2) and R2 bit ((c>>1)&1) + // Parent mapping: c=0,1→p00 c=2,3→p01 c=4,5→p10 c=6,7→p11 + const uint32_t parents[4] = {p00, p01, p10, p11}; + uint32_t all = 0; + for(int c = 0; c < 8; c++) { + uint32_t m = R3_INDIVIDUAL[c][lut]; + if(target3 == 0) m = ~m; + m &= parents[c >> 1]; + m &= r4_mask; + leaf_masks[c] = m; + all |= m; + } + + return all; +} diff --git a/applications/system/mfkey/mfkey_batch_prelude.h b/applications/system/mfkey/mfkey_batch_prelude.h new file mode 100644 index 000000000..7e113bc04 --- /dev/null +++ b/applications/system/mfkey/mfkey_batch_prelude.h @@ -0,0 +1,12 @@ +#ifndef MFKEY_BATCH_PRELUDE_H +#define MFKEY_BATCH_PRELUDE_H + +#include +#include "mfkey.h" + +// Returns lane survival mask AND per-child leaf masks. +// leaf_masks[c] (c=0..7) gives lanes where child c survives R0-R3+R4. +uint32_t + batch_prelude_unified(uint32_t batch_base, int oks, uint32_t r4_mask, uint32_t leaf_masks[8]); + +#endif // MFKEY_BATCH_PRELUDE_H diff --git a/applications/system/mfkey/mfkey_bs_verify.c b/applications/system/mfkey/mfkey_bs_verify.c new file mode 100644 index 000000000..6f312ba0f --- /dev/null +++ b/applications/system/mfkey/mfkey_bs_verify.c @@ -0,0 +1,530 @@ +// 32-way SWAR (SIMD Within A Register) verification of candidate LFSR states. +// Each bit position in a uint32_t represents one of 32 parallel lanes. + +#pragma GCC optimize("O3") + +#include "mfkey_bs_verify.h" +#include "crypto1.h" +#include + +// VFP register parking: use the M4F's 32 FPU registers (s0-s31) to stash +// slow-changing values during filter execution, freeing GP registers. + +#if defined(__arm__) && defined(__ARM_FP) +#define VFP_PARK(var, slot) \ + float slot; \ + __asm__ volatile("vmov %0, %1" : "=t"(slot) : "r"(var)) +#define VFP_UNPARK(var, slot) __asm__ volatile("vmov %0, %1" : "=r"(var) : "t"(slot)) +#else +#define VFP_PARK(var, slot) uint32_t slot = (var) +#define VFP_UNPARK(var, slot) (var) = (slot) +#endif + +// Bit-sliced filter function (minimized sum-of-products form) + +static inline __attribute__((always_inline)) uint32_t + crypto1_lut_a(uint32_t d, uint32_t c, uint32_t b, uint32_t a) { + return (c & d) | (a & c & ~b) | (a & d & ~b) | (b & ~c & ~d); +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_lut_b(uint32_t d, uint32_t c, uint32_t b, uint32_t a) { + return (b & c & d) | (a & b & ~c) | (c & ~b & ~d) | (d & ~a & ~b); +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_filter(const uint32_t* odd, uint32_t head) { + const uint32_t* p = odd + head; + uint32_t f4 = crypto1_lut_a(p[3], p[2], p[1], p[0]); + uint32_t f3 = crypto1_lut_b(p[7], p[6], p[5], p[4]); + uint32_t f2 = crypto1_lut_a(p[11], p[10], p[9], p[8]); + uint32_t f1 = crypto1_lut_a(p[15], p[14], p[13], p[12]); + uint32_t f0 = crypto1_lut_b(p[19], p[18], p[17], p[16]); + + uint32_t f32 = f3 & f2; + uint32_t res = (f32 & (f0 | f1)); + res |= (f4 & ((f1 & f3) | ~(f0 | f3))); + res |= (f0 & ~f2 & ((f1 & ~f4) | ~(f1 | f3))); + return res; +} + +// LFSR polynomial tap XOR functions + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_xor_taps_odd(const uint32_t* reg, uint32_t head) { + const uint32_t* p = reg + head; + uint32_t acc0 = p[2] ^ p[3] ^ p[4]; + uint32_t acc1 = p[6] ^ p[9] ^ p[10]; + uint32_t acc2 = p[11] ^ p[14] ^ p[15]; + uint32_t acc3 = p[16] ^ p[19] ^ p[21]; + return acc0 ^ acc1 ^ acc2 ^ acc3; +} + +static inline __attribute__((always_inline)) uint32_t + crypto1_bs_xor_taps_even(const uint32_t* reg, uint32_t head) { + const uint32_t* p = reg + head; + uint32_t acc0 = p[2] ^ p[11] ^ p[16]; + uint32_t acc1 = p[17] ^ p[18] ^ p[23]; + return acc0 ^ acc1; +} + +static inline __attribute__((always_inline)) uint32_t + poly_even_rollback_xor(const uint32_t* even, uint32_t head) { + const uint32_t* p = even + head; + return p[2] ^ p[11] ^ p[16] ^ p[17] ^ p[18]; +} + +// 32x32 SWAR butterfly transpose + +static void transpose_32x32(uint32_t* d) { + uint32_t t, r0, r1, r2, r3, r4, r5, r6, r7; + + // Step 1: 16x16 blocks + for(int i = 0; i < 16; i++) { + t = (d[i] >> 16 ^ d[i + 16]) & 0x0000FFFF; + d[i] ^= t << 16; + d[i + 16] ^= t; + } + + // Step 2: 8x8 blocks + for(int i = 0; i < 32; i += 16) { + for(int j = 0; j < 8; j++) { + t = (d[i + j] >> 8 ^ d[i + j + 8]) & 0x00FF00FF; + d[i + j] ^= t << 8; + d[i + j + 8] ^= t; + } + } + + // Steps 3-5: Process in 8-row blocks entirely in registers + for(int b = 0; b < 32; b += 8) { + r0 = d[b + 0]; + r1 = d[b + 1]; + r2 = d[b + 2]; + r3 = d[b + 3]; + r4 = d[b + 4]; + r5 = d[b + 5]; + r6 = d[b + 6]; + r7 = d[b + 7]; + + // Step 3: 4x4 blocks + t = (r0 >> 4 ^ r4) & 0x0F0F0F0F; + r0 ^= t << 4; + r4 ^= t; + t = (r1 >> 4 ^ r5) & 0x0F0F0F0F; + r1 ^= t << 4; + r5 ^= t; + t = (r2 >> 4 ^ r6) & 0x0F0F0F0F; + r2 ^= t << 4; + r6 ^= t; + t = (r3 >> 4 ^ r7) & 0x0F0F0F0F; + r3 ^= t << 4; + r7 ^= t; + + // Step 4: 2x2 blocks + t = (r0 >> 2 ^ r2) & 0x33333333; + r0 ^= t << 2; + r2 ^= t; + t = (r1 >> 2 ^ r3) & 0x33333333; + r1 ^= t << 2; + r3 ^= t; + t = (r4 >> 2 ^ r6) & 0x33333333; + r4 ^= t << 2; + r6 ^= t; + t = (r5 >> 2 ^ r7) & 0x33333333; + r5 ^= t << 2; + r7 ^= t; + + // Step 5: 1x1 blocks + t = (r0 >> 1 ^ r1) & 0x55555555; + r0 ^= t << 1; + r1 ^= t; + t = (r2 >> 1 ^ r3) & 0x55555555; + r2 ^= t << 1; + r3 ^= t; + t = (r4 >> 1 ^ r5) & 0x55555555; + r4 ^= t << 1; + r5 ^= t; + t = (r6 >> 1 ^ r7) & 0x55555555; + r6 ^= t << 1; + r7 ^= t; + + d[b + 0] = r0; + d[b + 1] = r1; + d[b + 2] = r2; + d[b + 3] = r3; + d[b + 4] = r4; + d[b + 5] = r5; + d[b + 6] = r6; + d[b + 7] = r7; + } +} + +void bs_init_from_candidates(Crypto1BitSlice* bs, const BsCandidateBatch* batch) { + bs->odd_head = 0; + bs->even_head = 0; + + uint32_t temp_odd[32]; + uint32_t temp_even[32]; + + int count = batch->count; + if(count >= 32) { + memcpy(temp_odd, batch->odd, 32 * sizeof(uint32_t)); + memcpy(temp_even, batch->even, 32 * sizeof(uint32_t)); + } else { + memcpy(temp_odd, batch->odd, count * sizeof(uint32_t)); + memcpy(temp_even, batch->even, count * sizeof(uint32_t)); + memset(temp_odd + count, 0, (32 - count) * sizeof(uint32_t)); + memset(temp_even + count, 0, (32 - count) * sizeof(uint32_t)); + } + + transpose_32x32(temp_odd); + transpose_32x32(temp_even); + + for(int i = 0; i < 24; i++) { + bs->odd[i] = temp_odd[i]; + bs->odd[i + 24] = temp_odd[i]; + bs->even[i] = temp_even[i]; + bs->even[i + 24] = temp_even[i]; + } +} + +// Rollback without keystream collection +static inline __attribute__((always_inline)) void + bs_rollback_word_noret(Crypto1BitSlice* bs, uint32_t in, uint32_t fb_mask) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + // Process first 16 bits (i = 31..16) + for(int i = 31; i >= 16; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; // Crypto1 big-endian bit ordering + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Broadcast bit to all 32 lanes + + uint32_t extracted = even_ptr[eh]; + VFP_PARK(extracted, _vfp_extracted); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(extracted, _vfp_extracted); + uint32_t new_eh = eh + 1; + + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + } + + // Intermediate normalization + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + + // Process remaining 16 bits (i = 15..0) + for(int i = 15; i >= 0; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Sign-extend bit without UB + + uint32_t extracted = even_ptr[eh]; + VFP_PARK(extracted, _vfp_extracted); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(extracted, _vfp_extracted); + uint32_t new_eh = eh + 1; + + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + } + + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + + bs->odd_head = oh; + bs->even_head = eh; +} + +static inline __attribute__((always_inline)) void + bs_crypt_word_noret(Crypto1BitSlice* bs, uint32_t in, uint32_t enc_mask) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 0; i < 32; i++) { + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; // Sign-extend bit without UB + + VFP_PARK(in_bits, _vfp_in_bits); + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + VFP_UNPARK(in_bits, _vfp_in_bits); + uint32_t feed = (ks & enc_mask) ^ in_bits; + feed ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + feed ^= crypto1_bs_xor_taps_even(even_ptr, eh); + + uint32_t new_eh = (eh == 0) ? 23 : (eh - 1); + + even_ptr[new_eh] = feed; + even_ptr[new_eh + 24] = feed; + eh = new_eh; + + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + } + + bs->odd_head = oh; + bs->even_head = eh; +} + +// Fused rollback + keystream comparison with byte-boundary early exit. +// fb_mask: 0 when keystream does not feed back into LFSR. + +static inline __attribute__((always_inline)) uint32_t bs_rollback_word_check_ks( + Crypto1BitSlice* bs, + uint32_t in, + uint32_t fb_mask, + uint32_t expected, + uint32_t alive) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 31; i >= 0; i--) { + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + + uint32_t extracted = even_ptr[eh]; + uint32_t new_eh = eh + 1; + + // Park values not needed during filter in VFP registers + VFP_PARK(alive, _vfp_alive); + VFP_PARK(extracted, _vfp_extracted); + VFP_PARK(new_eh, _vfp_new_eh); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + + // Restore from VFP + VFP_UNPARK(alive, _vfp_alive); + VFP_UNPARK(extracted, _vfp_extracted); + VFP_UNPARK(new_eh, _vfp_new_eh); + + // Compare keystream bit against expected + uint32_t exp_broadcast = ((expected >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + alive &= ~(ks ^ exp_broadcast); + + // Rollback LFSR step + uint32_t recovered_msb = extracted; + recovered_msb ^= poly_even_rollback_xor(even_ptr, new_eh); + recovered_msb ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + recovered_msb ^= in_bits; + recovered_msb ^= (ks & fb_mask); + + even_ptr[new_eh + 23] = recovered_msb; + if(new_eh + 23 >= 24) even_ptr[new_eh + 23 - 24] = recovered_msb; + + eh = new_eh; + + // Early exit at byte boundaries + if((i & 7) == 0 && !alive) { + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + bs->odd_head = oh; + bs->even_head = eh; + return 0; + } + } + + if(oh >= 24) oh -= 24; + if(eh >= 24) eh -= 24; + bs->odd_head = oh; + bs->even_head = eh; + return alive; +} + +// Fused forward crypt + keystream comparison with byte-boundary early exit. +// enc_mask: 0 when keystream does not feed back into LFSR. + +static inline __attribute__((always_inline)) uint32_t bs_crypt_word_check_ks( + Crypto1BitSlice* bs, + uint32_t in, + uint32_t enc_mask, + uint32_t expected, + uint32_t alive) { + uint32_t* odd_ptr = bs->odd; + uint32_t* even_ptr = bs->even; + uint32_t oh = bs->odd_head; + uint32_t eh = bs->even_head; + + for(int i = 0; i < 32; i++) { + int bit_pos = 24 ^ i; + uint32_t in_bits = ((in >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + + // Park alive in VFP during heavy filter computation + VFP_PARK(alive, _vfp_alive); + + uint32_t ks = crypto1_bs_filter(odd_ptr, oh); + + // Restore alive from VFP + VFP_UNPARK(alive, _vfp_alive); + + // Compare keystream bit against expected + uint32_t exp_broadcast = ((expected >> bit_pos) & 1) ? 0xFFFFFFFF : 0; + alive &= ~(ks ^ exp_broadcast); + + // LFSR advance + uint32_t feed = (ks & enc_mask) ^ in_bits; + feed ^= crypto1_bs_xor_taps_odd(odd_ptr, oh); + feed ^= crypto1_bs_xor_taps_even(even_ptr, eh); + + uint32_t new_eh = (eh == 0) ? 23 : (eh - 1); + even_ptr[new_eh] = feed; + even_ptr[new_eh + 24] = feed; + eh = new_eh; + + uint32_t* tmp_ptr = odd_ptr; + odd_ptr = even_ptr; + even_ptr = tmp_ptr; + uint32_t tmp_head = oh; + oh = eh; + eh = tmp_head; + + // Early exit at byte boundaries + if((i & 7) == 7 && !alive) { + bs->odd_head = oh; + bs->even_head = eh; + return 0; + } + } + + bs->odd_head = oh; + bs->even_head = eh; + return alive; +} + +// mfkey32 verification kernel +uint32_t bs_verify_batch_32(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive) { + // Checkpoint 1: rollback with keystream check + uint32_t expected1 = nonce->ar0_enc ^ nonce->p64; + alive = bs_rollback_word_check_ks(bs, 0, 0, expected1, alive); + if(!alive) return 0; + + // Intermediate rollback/crypt (no keystream check) + bs_rollback_word_noret(bs, nonce->nr0_enc, 0xFFFFFFFF); + bs_rollback_word_noret(bs, nonce->uid_xor_nt0, 0); + bs_crypt_word_noret(bs, nonce->uid_xor_nt1, 0); + bs_crypt_word_noret(bs, nonce->nr1_enc, 0xFFFFFFFF); + + // Checkpoint 2: forward crypt with keystream check + uint32_t expected2 = nonce->ar1_enc ^ nonce->p64b; + alive = bs_crypt_word_check_ks(bs, 0, 0, expected2, alive); + + return alive; +} + +void bs_extract_key(const BsCandidateBatch* batch, int lane, MfClassicNonce* nonce) { + struct Crypto1State t; + t.odd = batch->odd[lane]; + t.even = batch->even[lane]; + + if(nonce->attack == mfkey32) { + napi_lfsr_rollback_word(&t, 0, 0); + rollback_word_noret(&t, nonce->nr0_enc, 1); + rollback_word_noret(&t, nonce->uid_xor_nt0, 0); + } else if(nonce->attack == static_nested) { + rollback_word_noret(&t, nonce->uid_xor_nt1, 0); + } else { + napi_lfsr_rollback_word(&t, nonce->uid_xor_nt0, 0); + } + + crypto1_get_lfsr(&t, &nonce->key); +} + +// static_nested verification kernel +uint32_t bs_verify_batch_32_nested(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive) { + // Step 1: Rollback uid_xor_nt1 (fb=0) — no keystream check + bs_rollback_word_noret(bs, nonce->uid_xor_nt1, 0); + + // Step 2: Forward crypt uid_xor_nt0 (enc_mask=0) with keystream check + alive = bs_crypt_word_check_ks(bs, nonce->uid_xor_nt0, 0, nonce->ks1_1_enc, alive); + + return alive; +} + +// Scalar parity validation (cold path, typically 0-2 survivors) +static uint32_t validate_survivors_parity( + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive) { + uint32_t parity_valid = 0; + uint32_t remaining = alive; + while(remaining) { + int lane = __builtin_ctz(remaining); + remaining &= remaining - 1; + + struct Crypto1State t; + t.odd = batch->odd[lane]; + t.even = batch->even[lane]; + napi_lfsr_rollback_word(&t, nonce->uid_xor_nt0, 0); + + uint8_t pk; + struct Crypto1State temp = {t.odd, t.even}; + if((crypt_word_par(&temp, nonce->uid_xor_nt0, 0, nonce->nt0, &pk) == nonce->ks1_1_enc) && + (pk == nonce->par_1)) { + parity_valid |= (1U << lane); + } + } + return parity_valid; +} + +// static_encrypted verification kernel +// Hybrid: bitsliced keystream pruning + scalar parity on survivors. +uint32_t bs_verify_batch_32_encrypted( + Crypto1BitSlice* bs, + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive) { + alive = bs_rollback_word_check_ks(bs, nonce->uid_xor_nt0, 0, nonce->ks1_1_enc, alive); + if(!alive) return 0; + return validate_survivors_parity(batch, nonce, alive); +} diff --git a/applications/system/mfkey/mfkey_bs_verify.h b/applications/system/mfkey/mfkey_bs_verify.h new file mode 100644 index 000000000..2555626f0 --- /dev/null +++ b/applications/system/mfkey/mfkey_bs_verify.h @@ -0,0 +1,71 @@ +#ifndef MFKEY_BS_VERIFY_H +#define MFKEY_BS_VERIFY_H + +// Bit-sliced verification for all attack types (mfkey32, static_nested, +// static_encrypted). Processes 32 candidate LFSR states in parallel using +// SWAR (SIMD Within A Register) techniques. + +#include "mfkey.h" +#include +#include + +#define BS_BATCH_SIZE 32 + +typedef struct { + uint32_t odd[BS_BATCH_SIZE]; // Odd half-states (24-bit, lower bits used) + uint32_t even[BS_BATCH_SIZE]; // Even half-states (24-bit) + int count; // Current fill level (0-32) +} BsCandidateBatch; + +// Mirrored runway buffer: bits 0-23 mirrored to 24-47. +// Max read index: head (0-23) + max tap offset (23) = 46 < 48. +typedef struct { + uint32_t odd[48]; // 24 LFSR bits mirrored 2 times to avoid bounds checks + uint32_t even[48]; // Each element holds 32 bits (one per lane) + uint32_t odd_head; // Current head position (0-23 normalized) + uint32_t even_head; +} Crypto1BitSlice; + +// Initialize empty batch +static inline void bs_batch_init(BsCandidateBatch* batch) { + batch->count = 0; +} + +// Add candidate to batch +// Returns true if batch is now full (caller should verify and reset) +static inline bool bs_batch_add(BsCandidateBatch* batch, uint32_t odd, uint32_t even) { + if(batch->count < BS_BATCH_SIZE) { + batch->odd[batch->count] = odd; + batch->even[batch->count] = even; + batch->count++; + } + return batch->count >= BS_BATCH_SIZE; +} + +// Initialize bitsliced state from candidate batch (transpose scalar → SWAR) +// Call once before dispatching to any verification kernel +void bs_init_from_candidates(Crypto1BitSlice* bs, const BsCandidateBatch* batch); + +// Verify batch of up to 32 candidates against nonce data +// bs: pre-initialized bitsliced state (from bs_init_from_candidates) +// alive: lane mask with zero-states already rejected +// Returns bitmask where bit i is set if candidate i is valid +uint32_t bs_verify_batch_32(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive); + +// Extract key from valid lane (unified for all attack types) +// lane = __builtin_ctz(valid_mask) to get first valid lane +void bs_extract_key(const BsCandidateBatch* batch, int lane, MfClassicNonce* nonce); + +// Static nested verification: rollback uid_xor_nt1 → forward crypt uid_xor_nt0 +// Returns bitmask of valid lanes (first-match) +uint32_t bs_verify_batch_32_nested(Crypto1BitSlice* bs, MfClassicNonce* nonce, uint32_t alive); + +// Static encrypted verification: rollback uid_xor_nt0 + parity check +// Returns bitmask of ALL valid lanes (multi-result) +uint32_t bs_verify_batch_32_encrypted( + Crypto1BitSlice* bs, + const BsCandidateBatch* batch, + MfClassicNonce* nonce, + uint32_t alive); + +#endif // MFKEY_BS_VERIFY_H diff --git a/applications/system/mfkey/mfkey_dedup.h b/applications/system/mfkey/mfkey_dedup.h new file mode 100644 index 000000000..b6c193bd1 --- /dev/null +++ b/applications/system/mfkey/mfkey_dedup.h @@ -0,0 +1,141 @@ +#ifndef MFKEY_DEDUP_H +#define MFKEY_DEDUP_H + +#include "mfkey.h" +#include "crypto1.h" +#include +#include + +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Golden ratio multiply-shift hash for 20-bit semi-states → 11-bit output +#define FIB_HASH_20BIT(x) (((x) * 2654435769u) >> 21) + +// Eliminates candidates before tree expansion by checking rounds 1-3 in advance. +// Returns 1 if any path survives to round 4, else 0. +static inline __attribute__((always_inline)) int + prefilter_rounds_1_3(uint32_t semi_state, int oks) { + OPT_BARRIER(oks); // Prevent hoisting of BIT(oks, round) extractions + // Precompute nibble bits for rounds 1-3 + // Round 1: nibble = (semi_state >> 15) & 0xF + // Round 2: nibble = (semi_state >> 14) & 0xF + // Round 3: nibble = (semi_state >> 13) & 0xF + uint32_t nib1 = ((0x0d938 >> ((semi_state >> 15) & 0xF)) & 1); + uint32_t nib2 = ((0x0d938 >> ((semi_state >> 14) & 0xF)) & 1); + uint32_t nib3 = ((0x0d938 >> ((semi_state >> 13) & 0xF)) & 1); + + // Round 1: 2 potential children -> 2-bit mask + uint32_t v = semi_state << 1; + int target = BIT(oks, 1); + uint32_t fp = filter_pair_with_nib(v, nib1); + int f0 = FILTER_F0(fp); + int f1 = FILTER_F1(fp); + + // r1_valid: bit 0 = v survives, bit 1 = v|1 survives + int r1_valid = ((f0 == target) << 0) | ((f1 == target) << 1); + if(!r1_valid) return 0; + + // Round 2: up to 4 grandchildren -> 4-bit mask + // Tracks exactly which grandchildren survive + target = BIT(oks, 2); + int r2_valid = 0; + + if(r1_valid & 1) { // v survived R1 + fp = filter_pair_with_nib(v << 1, nib2); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target) r2_valid |= 1; // v<<1 survives + if(f1 == target) r2_valid |= 2; // (v<<1)|1 survives + } + if(r1_valid & 2) { // v|1 survived R1 + fp = filter_pair_with_nib((v | 1) << 1, nib2); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target) r2_valid |= 4; // (v|1)<<1 survives + if(f1 == target) r2_valid |= 8; // ((v|1)<<1)|1 survives + } + + if(!r2_valid) return 0; + + // Round 3: only check children of actual R2 survivors + target = BIT(oks, 3); + + if(r2_valid & 1) { // v<<1 survived R2 + fp = filter_pair_with_nib(v << 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 2) { // (v<<1)|1 survived R2 + fp = filter_pair_with_nib((v << 2) | 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 4) { // (v|1)<<1 survived R2 + fp = filter_pair_with_nib((v | 1) << 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + if(r2_valid & 8) { // ((v|1)<<1)|1 survived R2 + fp = filter_pair_with_nib(((v | 1) << 2) | 2, nib3); + f0 = FILTER_F0(fp); + f1 = FILTER_F1(fp); + if(f0 == target || f1 == target) return 1; + } + + return 0; +} + +// 8x unrolled Duff's device for duplicate detection in packed 24-bit states +static inline __attribute__((always_inline)) bool scan_for_duplicate_8x( + const uint8_t* states, // Packed 24-bit state array + int count, // Number of entries to scan + uint32_t state_masked) // Target state with MSB masked off (& 0x00FFFFFF) +{ + if(count <= 0) return false; + + const uint8_t* p = states; + int n = (count + 7) / 8; // Number of 8-iteration chunks (rounded up) + + // Duff's Device: jump into unrolled loop based on remainder + switch(count % 8) { + case 0: + do { + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 7: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 6: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 5: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 4: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 3: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 2: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + __attribute__((fallthrough)); + case 1: + if((*(uint32_t*)p & 0x00FFFFFF) == state_masked) return true; + p += 3; + } while(--n > 0); + } + return false; +} + +#endif // MFKEY_DEDUP_H diff --git a/applications/system/mfkey/mfkey_recovery.c b/applications/system/mfkey/mfkey_recovery.c new file mode 100644 index 000000000..8ce52cffd --- /dev/null +++ b/applications/system/mfkey/mfkey_recovery.c @@ -0,0 +1,288 @@ +// MFKey Recovery - Sorting and LFSR state verification +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_recovery.h" +#include "crypto1.h" +#include "mfkey_bs_verify.h" +#include + +// External dependencies from mfkey.c +extern int sync_state(ProgramState* program_state); +extern void flush_key_buffer(ProgramState* program_state); +extern uint8_t MSB_LIMIT; + +// External from mfkey_attack.c +extern volatile bool g_abort_attack; +extern ProgramState* g_program_state; + +static inline __attribute__((always_inline)) int + binsearch(unsigned int data[], int start, int stop) { + unsigned int msb_val = data[stop] & 0xff000000; + + while(start != stop) { + int mid = start + ((stop - start) >> 1); + if(data[mid] >= msb_val) { + stop = mid; + } else { + start = mid + 1; + } + } + return start; +} + +// Radix sort: O(n) non-recursive replacement for quicksort +#define RADIX_BITS 8 +#define RADIX_SIZE (1 << RADIX_BITS) +#define RADIX_MASK (RADIX_SIZE - 1) + +unsigned int g_radix_temp[1280]; + +void radix_sort_32(unsigned int* arr, int low, int high, unsigned int* temp) { + int count = high - low + 1; + if(count <= 1) return; + + // Small arrays use insertion sort + if(count < 32) { + for(int i = low + 1; i <= high; i++) { + unsigned int key = arr[i]; + int j = i - 1; + while(j >= low && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } + return; + } + + if(count > 1280) { + count = 1280; + high = low + count - 1; + } + + unsigned int hist[RADIX_SIZE]; + unsigned int* src = arr + low; + unsigned int* dst = temp; + + for(int pass = 0; pass < 4; pass++) { + int shift = pass * RADIX_BITS; + memset(hist, 0, sizeof(hist)); + for(int i = 0; i < count; i++) { + hist[(src[i] >> shift) & RADIX_MASK]++; + } + unsigned int total = 0; + for(int i = 0; i < RADIX_SIZE; i++) { + unsigned int c = hist[i]; + hist[i] = total; + total += c; + } + for(int i = 0; i < count; i++) { + int bucket = (src[i] >> shift) & RADIX_MASK; + dst[hist[bucket]++] = src[i]; + } + unsigned int* t = src; + src = dst; + dst = t; + } +} + +static inline __attribute__((always_inline)) void + update_contribution_odd(unsigned int data[], int item) { + unsigned int val = data[item]; + unsigned int p = val >> 25; + p = p << 1 | evenparity32(val & CONST_M1_1); + p = p << 1 | evenparity32(val & CONST_M2_1); + data[item] = (p << 24) | (val & 0xffffff); +} + +static inline __attribute__((always_inline)) void + update_contribution_even(unsigned int data[], int item) { + unsigned int val = data[item]; + unsigned int p = val >> 25; + p = p << 1 | evenparity32(val & CONST_M1_2); + p = p << 1 | evenparity32(val & CONST_M2_2); + data[item] = (p << 24) | (val & 0xffffff); +} + +static int __attribute__((hot)) extend_table_odd(unsigned int data[], int tbl, int end, int bit) { + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + int f0 = filter(data[tbl]); + int f1 = filter(data[tbl] | 1); + if((f0 ^ f1) != 0) { + data[tbl] |= f0 ^ bit; + update_contribution_odd(data, tbl); + } else if(f0 == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution_odd(data, tbl); + tbl++; + update_contribution_odd(data, tbl); + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +static int __attribute__((hot)) +extend_table_even(unsigned int data[], int tbl, int end, int bit, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + int f0 = filter(data[tbl]); + int f1 = filter(data[tbl] | 1); + if((f0 ^ f1) != 0) { + data[tbl] |= f0 ^ bit; + update_contribution_even(data, tbl); + data[tbl] ^= in; + } else if(f0 == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution_even(data, tbl); + data[tbl++] ^= in; + update_contribution_even(data, tbl); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +// Routes to correct bitsliced kernel by attack type. +// Returns: -1 = key found (mfkey32/static_nested), 0 = no match. +// For static_encrypted: buffers all valid keys, always returns 0. +static __attribute__((noinline)) int + bs_verify_dispatch(BsCandidateBatch* batch, MfClassicNonce* n) { + // Common: compute alive mask and reject zero states + uint32_t alive = (batch->count >= 32) ? 0xFFFFFFFF : ((1U << batch->count) - 1); + for(int i = 0; i < batch->count; i++) { + if(!(batch->odd[i] | batch->even[i])) { + alive &= ~(1U << i); + } + } + if(!alive) return 0; + + // Common: transpose scalar candidates → bitsliced SWAR state (once) + Crypto1BitSlice bs; + bs_init_from_candidates(&bs, batch); + + if(n->attack == mfkey32) { + uint32_t valid = bs_verify_batch_32(&bs, n, alive); + if(valid) { + int lane = __builtin_ctz(valid); + bs_extract_key(batch, lane, n); + return -1; + } + } else if(n->attack == static_nested) { + uint32_t valid = bs_verify_batch_32_nested(&bs, n, alive); + if(valid) { + int lane = __builtin_ctz(valid); + bs_extract_key(batch, lane, n); + return -1; + } + } else { + // static_encrypted: collect ALL valid keys + uint32_t valid = bs_verify_batch_32_encrypted(&bs, batch, n, alive); + while(valid) { + int lane = __builtin_ctz(valid); + valid &= valid - 1; + bs_extract_key(batch, lane, n); + // Buffer all valid keys for static_encrypted + g_program_state->num_candidates++; + g_program_state->key_buffer[g_program_state->key_buffer_count] = n->key; + g_program_state->key_idx_buffer[g_program_state->key_buffer_count] = n->key_idx; + g_program_state->key_buffer_count++; + if(g_program_state->key_buffer_count >= g_program_state->key_buffer_size) { + flush_key_buffer(g_program_state); + } + } + } + return 0; +} + +// 32-way parallel verification with recursive MSB-walk +int old_recover_bs( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run) { + int o, e, i; + + if(rem == -1) { + BsCandidateBatch batch; + bs_batch_init(&batch); + + for(e = e_head; e <= e_tail; ++e) { + uint32_t e_val = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + + for(o = o_head; o <= o_tail; ++o, ++s) { + uint32_t o_val = odd[o]; + uint32_t final_even = o_val; + uint32_t final_odd = e_val ^ evenparity32(o_val & LF_POLY_ODD); + + if(bs_batch_add(&batch, final_odd, final_even)) { + int result = bs_verify_dispatch(&batch, n); + if(result == -1) return -1; + if(g_abort_attack) return -2; + bs_batch_init(&batch); + } + } + } + + if(batch.count > 0) { + int result = bs_verify_dispatch(&batch, n); + if(result == -1) return -1; + } + + return s; + } + + if(first_run) { + // All elements share the same top byte (constructed as raw | msb_val), + // so sorting is a no-op and the MSB-walk finds exactly one group. + // Skip directly to recursion with first_run=0. + return old_recover_bs( + odd, o_head, o_tail, oks, even, e_head, e_tail, eks, rem, s, n, in, 0); + } + + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table_odd(odd, o_head, o_tail, oks & 1); + if(o_head > o_tail) return s; + e_tail = extend_table_even(even, e_head, e_tail, eks & 1, in & 3); + if(e_head > e_tail) return s; + } + + // Sort by MSB for grouped cross-product intersection + radix_sort_32(odd, o_head, o_tail, g_radix_temp); + radix_sort_32(even, e_head, e_tail, g_radix_temp); + + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover_bs(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, n, in, 0); + if(s < 0) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} diff --git a/applications/system/mfkey/mfkey_recovery.h b/applications/system/mfkey/mfkey_recovery.h new file mode 100644 index 000000000..257eac476 --- /dev/null +++ b/applications/system/mfkey/mfkey_recovery.h @@ -0,0 +1,31 @@ +#ifndef MFKEY_RECOVERY_H +#define MFKEY_RECOVERY_H + +#include "mfkey.h" +#include "crypto1.h" + +// Scratch buffer for radix sort. Also reused as ping-pong buffer by +// state_loop_r4 during expansion (phases don't overlap). +extern unsigned int g_radix_temp[1280]; + +// Radix sort for 24-bit packed states (O(n) non-recursive sorting) +void radix_sort_32(unsigned int* arr, int low, int high, unsigned int* temp); + +// Bitsliced recovery: 32-way parallel verification +// Returns: >0 = states checked, -1 = key found, -2 = aborted +int old_recover_bs( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run); + +#endif // MFKEY_RECOVERY_H diff --git a/applications/system/mfkey/mfkey_state_expansion.c b/applications/system/mfkey/mfkey_state_expansion.c new file mode 100644 index 000000000..a3071886e --- /dev/null +++ b/applications/system/mfkey/mfkey_state_expansion.c @@ -0,0 +1,152 @@ +// MFKey State Expansion - LFSR tree search through rounds 1-12 +// Extracted from mfkey_attack.c for better code organization + +#pragma GCC optimize("O3") + +#include "mfkey_state_expansion.h" +#include "mfkey_recovery.h" +#include "crypto1.h" +#include + +#define OPT_BARRIER(x) __asm__ volatile("" : "+r"(x)) + +// Pre-fold xks_bit into the filter constant so BIT(adj, idx) == BIT(0xEC57E80A, idx) ^ xks_bit +#define ADJ_FILTER(xks, round) (0xEC57E80Au ^ (-(uint32_t)BIT(xks, round))) + +// In-place LFSR expansion for rounds 4-12, called after batch prelude completes rounds 0-3. +// fork_delta deferred past round 4 early-exit (~95% of calls exit at round 4). +__attribute__((hot)) int state_loop_r4( + unsigned int* states_buffer, + int count, // Number of active states in buffer + int xks, + int m1, + int m2, + unsigned int in, + int and_val) { + OPT_BARRIER(xks); // Prevent hoisting of BIT(xks, round) extractions + if(count == 0) return -1; + + // Round 4 (in-place, no parity update) + int states_tail = count - 1; + { + uint32_t adj = ADJ_FILTER(xks, 4); + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + // Single child survives + states_buffer[s] = v | f0_x; + } else if(__builtin_expect(f0_x == 0, 1)) { + // Both children survive — fork + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s] = v; + s++; + states_buffer[s] = v | 1; + } else { + // Neither survives — eliminate + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 1)) return -1; + } + + // Fork delta deferred past round 4 early-exit (~95% of calls don't reach here) + uint32_t fork_delta = ((m1 & 1) << 25) | ((m2 & 1) << 24) | 1; + + // Round 5 (unrolled, in-place) + { + uint32_t adj = ADJ_FILTER(xks, 5); + unsigned int r5_in = ((in >> 2) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ r5_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ r5_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + // Round 6 (unrolled, in-place) + { + uint32_t adj = ADJ_FILTER(xks, 6); + unsigned int r6_in = ((in >> 4) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ r6_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ r6_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + // Rounds 7-12 (loop, in-place) + for(int round = 7; round <= 12; ++round) { + uint32_t adj = ADJ_FILTER(xks, round); + unsigned int round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + for(int s = 0; s <= states_tail; ++s) { + unsigned int raw = states_buffer[s]; + OPT_BARRIER(raw); + unsigned int v = raw << 1; + uint32_t fp = filter_pair_xor(v, adj); + int f0_x = FILTER_F0(fp); + int f1_x = FILTER_F1(fp); + + if(__builtin_expect((f0_x ^ f1_x) != 0, 0)) { + v |= f0_x; + OPT_BARRIER(v); + states_buffer[s] = update_contribution_reg(v, m1, m2) ^ round_in; + } else if(__builtin_expect(f0_x == 0, 1)) { + states_buffer[++states_tail] = states_buffer[s + 1]; + OPT_BARRIER(v); + uint32_t p0 = update_contribution_reg(v, m1, m2) ^ round_in; + states_buffer[s] = p0; + s++; + states_buffer[s] = p0 ^ fork_delta; + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + if(__builtin_expect(states_tail < 0, 0)) return -1; + } + + return states_tail; +} diff --git a/applications/system/mfkey/mfkey_state_expansion.h b/applications/system/mfkey/mfkey_state_expansion.h new file mode 100644 index 000000000..77dd82da4 --- /dev/null +++ b/applications/system/mfkey/mfkey_state_expansion.h @@ -0,0 +1,17 @@ +#ifndef MFKEY_STATE_EXPANSION_H +#define MFKEY_STATE_EXPANSION_H + +#include + +// LFSR state expansion for rounds 4-12 (rounds 0-3 handled by batch prelude). +// Returns final state count (states_tail), or -1 if all eliminated. +int state_loop_r4( + unsigned int* states_buffer, + int count, + int xks, + int m1, + int m2, + unsigned int in, + int and_val); + +#endif // MFKEY_STATE_EXPANSION_H From 8accdb435eb49280eadc825dfe89ce6fa3c70ae8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 17 Feb 2026 01:11:28 +0300 Subject: [PATCH 075/202] upd changelog --- CHANGELOG.md | 51 +++------------------------------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e2adab8e..6a6f59fcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,54 +1,9 @@ ## Main changes - Current API: 87.6 -* SubGHz: **Cardin S449 protocol full support** (64bit keeloq) (with Add manually, and all button codes) (**use FSK12K modulation to read the remote**) (closes issues #735 #908) (by @xMasterX and @zero-mega (thanks!)) -* SubGHz: **Beninca ARC AES128 protocol full support** (128bit dynamic) (with Add manually, and 3 button codes) (resolves issue #596) (by @xMasterX and @zero-mega) -* SubGHz: **Treadmill37 protocol support** (37bit static) (by @xMasterX) -* SubGHz: **Jarolift protocol full support** (72bit dynamic) (with Add manually, and all button codes) (by @xMasterX & d82k & Steffen (bastelbudenbuben de)) -* SubGHz: **New modulation FSK with 12KHz deviation** -* SubGHz: **KingGates Stylo 4k - Add manually and button switch support** + refactoring of encoder -* SubGHz: **Stilmatic (R-Tech) - 12bit discr. fix & button 9 support** (two buttons hold simulation) (mapped on arrow keys) -* SubGHz: **Counter editor refactoring** (PR #939 | by @Dmitry422) -* SubGHz: **Alutech AT-4N & Nice Flor S turbo speedup** (PR #942 | by @Dmitry422) -* SubGHz: **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) -* SubGHz: **Sommer - last button code 0x6 support** (mapped on arrow keys) -* SubGHz: **V2 Phoenix (Phox) added 2 counter modes support** (docs updated) -* SubGHz: Add 390MHz, 430.5MHz to default hopper list (6 elements like in OFW) (works well with Hopper RSSI level set for your enviroment) -* SubGHz: Fixed button mapping for **FAAC RC/XT** -* SubGHz: KeeLoq **display decrypted hop** in `Hop` instead of showing encrypted as is (encrypted non byte reversed hop is still displayed in `Key` field) -* SubGHz: **BFT KeeLoq** try decoding with **zero seed** too -* SubGHz: KeeLoq **display BFT programming mode TX** (when arrow button is held) -* SubGHz: KeeLoq **add counter mode 7 (sends 7 signals increasing counter with 0x3333 steps)** - may bypass counter on some receivers! -* SubGHz: Add signals button editor and real **remote simulation** (full signal transmit with just one click) (PR #956 #958 | by @Dmitry422) -* SubGHz: TX Power setting (PR #960 | by @LeeroysHub) -* Desktop: BT ON/OFF instead of Lock/Unlock action in desktop lock menu (To lock - hold Up button) (PR #965 | by @Dmitry422) (idea by Dezederix) -* Desktop: Holding back button to power off menu now allows to open power settings on left button (idea by Dezederix) -* JS: feat: add IR capabilities to the JS engine (PR #957 | by @LuisMayo) -* NFC: Handle PPS request in ISO14443-4 layer (by @WillyJL) -* NFC: Fixes to `READ_MULTI` and `GET_BLOCK_SECURITY` commands in ISO 15693-3 emulation (by @WillyJL & @aaronjamt) -* Archive: Allow folders to be pinned (by @WillyJL) -* Apps: Build tag (**12feb2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422) +* Apps: Build tag (**17feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* Settings: Storage settings exit scenes properly if used via favourites (fixes issue #951) -* LCD: Backlight settings bug fix (PR #962 | by @Dmitry422) -* UI: Various small changes -* Desktop: Disable winter holidays anims -* OFW PR 4333: NFC: Fix sending 32+ byte ISO 15693-3 commands (by @WillyJL) -* NFC: Fix LED not blinking at SLIX unlock (closes issue #945) -* SubGHz: Added some RAM -* SubGHz: Fix documentation link for HT12A protocol (by @carlogrisetti) -* SubGHz: Improve docs on low level code (PR #949 | by @Dmitry422) -* SubGHz: Fix Alutech AT4N false positives -* SubGHz: Cleanup of extra local variables -* SubGHz: Replaced Cars ignore option with Revers RB2 protocol ignore option -* SubGHz: Moved Starline, ScherKhan, Kia decoders into external app -* SubGHz: Possible Sommer timings fix -* SubGHz: Various fixes -* SubGHz: Nice Flor S remove extra uint64 variable -* SubGHz: Rename Sommer(fsk476) to Sommer (Sommer keeloq works better with FM12K) + added backwards compatibility with older saved files -* Docs: Add full list of supported SubGHz protocols and their frequencies/modulations that can be used for reading remotes - [Docs Link](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/SubGHzSupportedSystems.md) -* Desktop: Show debug status (D) if clock is enabled and debug flag is on (PR #942 | by @Dmitry422) -* NFC: Fix some typos in Type4Tag protocol (by @WillyJL) -* Clangd: Add clangd parameters in IDE agnostic config file (by @WillyJL) +* MFKey: Update to v4.1 (by @noproto & @dchristle)

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 1dd8cfa00ab955a51e21b09d7930e7f08a39bc1a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:50:13 +0300 Subject: [PATCH 076/202] Fix USB HID keyboard LED state reporting OFW PR 4338 by Caballosanex --- targets/f7/furi_hal/furi_hal_usb_hid.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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; From f01005580ab818fb7f3f29076b2ee2ea7aea0b36 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:45:58 +0300 Subject: [PATCH 077/202] Make view_port_send_to_back public OFW PR 4320 by loftyinclination --- targets/f18/api_symbols.csv | 2 +- targets/f7/api_symbols.csv | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 38794afcc..ab29113fb 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1768,7 +1768,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 e3159a17e..136786c7d 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -2093,7 +2093,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*" From ad1fb5a248886c20e1d0b0ec61211779d5fffc29 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 16:47:53 +0300 Subject: [PATCH 078/202] upd changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a6f59fcd..7e8607072 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,10 @@ ## Main changes - Current API: 87.6 * SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422) -* Apps: Build tag (**17feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex) +* Apps: Build tag (**22feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* OFW PR 4320: API: Make view_port_send_to_back public (by @loftyinclination) * MFKey: Update to v4.1 (by @noproto & @dchristle)

#### Known NFC post-refactor regressions list: From d61c98c97c41c1d87e6f4962ca18ebb4ca152a7d Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:43:59 +0300 Subject: [PATCH 079/202] subghz keeloq genius btn fix, and change delta size --- lib/subghz/protocols/keeloq.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 0ed600ece..83e7fdc34 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -21,7 +21,7 @@ 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, }; @@ -177,7 +177,8 @@ static bool subghz_protocol_keeloq_gen_data( } 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, "NICE_Smilo") == 0) || + (strcmp(instance->manufacture_name, "Genius_Bravo") == 0)) { klq_last_custom_btn = 0xB; } else if( (strcmp(instance->manufacture_name, "Novoferm") == 0) || From e681fd2be571523f251ff688351a9299aa533ad4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:44:36 +0300 Subject: [PATCH 080/202] update docs --- documentation/SubGHzSupportedSystems.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index eed5ee420..1784b9db9 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -20,7 +20,7 @@ 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) (button code `0` emulates `hidden button` option on the remote) +- 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) - CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) - CAME TWEE `433.92MHz` `AM650` (54 bits, Static) @@ -32,11 +32,11 @@ 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) - 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) @@ -49,7 +49,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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) - 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) @@ -106,7 +106,7 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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) - 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) From 56620ab62f3204756332fb445fe7a6afa0cff624 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 19:44:57 +0300 Subject: [PATCH 081/202] subghz somfy keytis button switch and add manually support --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/helpers/subghz_gen_info.c | 11 +++- .../main/subghz/helpers/subghz_gen_info.h | 6 ++ .../helpers/subghz_txrx_create_protocol_key.c | 30 ++++++++++ .../helpers/subghz_txrx_create_protocol_key.h | 8 +++ .../subghz/scenes/subghz_scene_set_button.c | 5 ++ .../subghz/scenes/subghz_scene_set_counter.c | 16 +++++ .../subghz/scenes/subghz_scene_set_seed.c | 2 + .../subghz/scenes/subghz_scene_set_serial.c | 9 +++ .../subghz/scenes/subghz_scene_set_type.c | 13 +++- lib/subghz/protocols/somfy_keytis.c | 60 ++++++++++++++++--- 11 files changed, 150 insertions(+), 11 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 011b53025..4242e552d 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -70,6 +70,7 @@ typedef enum { SetTypeFaacSLH_433, SetTypeBFTMitto, SetTypeSomfyTelis, + SetTypeSomfyKeytis, SetTypeKingGatesStylo4k, SetTypeBenincaARC, SetTypeJarolift, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index bfa609e32..f9574d40d 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -523,6 +523,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 & 0x000FFFFF) | 0x0D500000, + .somfy_keytis.btn = 0x04, + .somfy_keytis.cnt = 0x03}; + break; case SetTypeKingGatesStylo4k: gen_info = (GenInfo){ .type = GenKingGatesStylo4k, @@ -636,7 +645,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; diff --git a/applications/main/subghz/helpers/subghz_gen_info.h b/applications/main/subghz/helpers/subghz_gen_info.h index a680b7ba5..478039c45 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -10,6 +10,7 @@ typedef enum { GenKeeloqBFT, GenAlutechAt4n, GenSomfyTelis, + GenSomfyKeytis, GenKingGatesStylo4k, GenBenincaARC, GenJarolift, @@ -64,6 +65,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; 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 27fb71f5b..72197c0c9 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -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, 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, 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 285770975..9cd75fad7 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -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, diff --git a/applications/main/subghz/scenes/subghz_scene_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index d2ef60a91..9f96f03d2 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_button.c +++ b/applications/main/subghz/scenes/subghz_scene_set_button.c @@ -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); @@ -98,6 +102,7 @@ bool subghz_scene_set_button_on_event(void* context, SceneManagerEvent event) { case GenBenincaARC: case GenJarolift: 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 8510d4aca..d9ad3a42c 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -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); @@ -125,6 +129,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); @@ -193,6 +200,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, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index 36307f11f..1ba7d4c61 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -30,6 +30,7 @@ void subghz_scene_set_seed_on_enter(void* context) { case GenKeeloq: case GenAlutechAt4n: case GenSomfyTelis: + case GenSomfyKeytis: case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: @@ -92,6 +93,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { case GenKeeloq: case GenAlutechAt4n: case GenSomfyTelis: + case GenSomfyKeytis: case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index f30b1a4a5..58fbbf026 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_serial.c +++ b/applications/main/subghz/scenes/subghz_scene_set_serial.c @@ -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); @@ -122,6 +126,10 @@ 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); @@ -159,6 +167,7 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { case GenKeeloqBFT: case GenAlutechAt4n: case GenSomfyTelis: + case GenSomfyKeytis: case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 2be40602b..e696664f5 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -16,6 +16,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeFaacSLH_433] = "FAAC SLH 433MHz", [SetTypeBFTMitto] = "BFT Mitto 433MHz", [SetTypeSomfyTelis] = "Somfy Telis 433MHz", + [SetTypeSomfyKeytis] = "Somfy Keytis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", [SetTypeRoger_433] = "Roger 433MHz", @@ -56,7 +57,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeCardinS449_433FM] = "KL: Cardin S449 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 433MHz", [SetTypeNiceMHouse_433_92] = "KL: Mhouse 433MHz", [SetTypeNiceSmilo_433_92] = "KL: Nice Smilo 433MHz", [SetTypeNiceFlorS_433_92] = "Nice FloR-S 433MHz", @@ -190,6 +191,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, @@ -296,6 +306,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { case GenKeeloqBFT: // 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) diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 38873d064..e673ad0ae 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 = { @@ -122,17 +124,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(&instance->generic.btn)) - FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->generic.btn); + 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) { @@ -156,7 +190,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; @@ -178,7 +212,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]; @@ -420,6 +454,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)) { @@ -711,6 +747,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); } /** From 95debd82c2a40db7d189e2235fb517d5e40b7c85 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:24:09 +0300 Subject: [PATCH 082/202] fix keytis variable usage --- lib/subghz/protocols/somfy_keytis.c | 44 +++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index e673ad0ae..2825ea7a0 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -26,7 +26,6 @@ struct SubGhzProtocolDecoderSomfyKeytis { uint16_t header_count; ManchesterState manchester_saved_state; - uint32_t press_duration_counter; }; struct SubGhzProtocolEncoderSomfyKeytis { @@ -223,7 +222,7 @@ static bool data <<= 8; data |= frame[i]; } - instance->generic.data_2 = data; + instance->generic.seed = data; return true; } @@ -239,12 +238,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_write_uint32( + flipper_format, "Duration_Counter", &instance->generic.seed, 1)) { + FURI_LOG_E(TAG, "Unable to add Duration_Counter"); + res = SubGhzProtocolStatusErrorParserOthers; + } + + return res == SubGhzProtocolStatusOk; } /** @@ -310,7 +322,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( @@ -388,7 +400,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( @@ -551,7 +563,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, @@ -628,8 +640,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++; @@ -797,7 +808,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; } @@ -823,10 +834,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; @@ -864,7 +872,7 @@ void subghz_protocol_decoder_somfy_keytis_get_string(void* context, FuriString* 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, From 317838eaf4194013fb00c9b1bf20b46b102e84c1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 20:33:06 +0300 Subject: [PATCH 083/202] fix keytis key generation --- applications/main/subghz/helpers/subghz_gen_info.c | 2 +- lib/subghz/protocols/somfy_keytis.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index f9574d40d..bce3d6856 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -528,7 +528,7 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .type = GenSomfyKeytis, .mod = "AM650", .freq = 433420000, - .somfy_keytis.serial = (key & 0x000FFFFF) | 0x0D500000, + .somfy_keytis.serial = (key & 0x0000FFFF) | 0x00D50000, .somfy_keytis.btn = 0x04, .somfy_keytis.cnt = 0x03}; break; diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 2825ea7a0..cf03f4b4d 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -250,7 +250,7 @@ bool subghz_protocol_somfy_keytis_create_data( } if((res == SubGhzProtocolStatusOk) && - !flipper_format_write_uint32( + !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; From 0c6140217832fd7c57e9c51faa2375283d1f0bb5 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:44:44 +0300 Subject: [PATCH 084/202] keeloq bft mitto experiments --- lib/subghz/protocols/keeloq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 83e7fdc34..2ad023f24 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -803,6 +803,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) { From a4ae9293217d2cb592210e77187fb69aacaf1453 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:48:23 +0300 Subject: [PATCH 085/202] upd changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e8607072..e65641fad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ## Main changes - Current API: 87.6 -* SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422) +* SubGHz: **BFT Mitto fix decode bug** (seed was not resetting after one successful decode) +* SubGHz: **Somfy Keytis** button switch and **Add Manually support** +* SubGHz: **KeeLoq** change delta size +* SubGHz: **Genius Echo/Bravo** add 2 buttons hold simulation (0xB btn code) +* SubGHz: Signal **Settings Improvements** (PR #968 | by @Dmitry422) * OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex) * Apps: Build tag (**22feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From 9aacbf943c458df1db94c05c0c08cc87bbc93bb0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Feb 2026 22:52:25 +0300 Subject: [PATCH 086/202] upd docs --- documentation/SubGHzSupportedSystems.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 1784b9db9..69ec4d4ec 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -49,7 +49,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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.42MHz, 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) From 485a705d5bebbe8bd583f832bd488591eed37f59 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:21:24 +0300 Subject: [PATCH 087/202] subghz: bft force seed value --- lib/subghz/protocols/keeloq.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 2ad023f24..421cfe560 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1023,10 +1023,10 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller_selector( 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; - } + //if(instance->seed == 0) { + instance->seed = (fix & 0xFFFFFFF); + reset_seed_back = true; + //} } man = subghz_protocol_keeloq_common_secure_learning( fix, instance->seed, manufacture_code->key); From 35474e3627a7c57a1d59906b113b2bb57b36b240 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 23 Feb 2026 04:30:26 +0300 Subject: [PATCH 088/202] subghz: bft experiments --- lib/subghz/protocols/keeloq.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 421cfe560..159dbc62a 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1023,11 +1023,23 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller_selector( case KEELOQ_LEARNING_SECURE: bool reset_seed_back = false; if((strcmp(furi_string_get_cstr(manufacture_code->name), "BFT") == 0)) { + // 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); From 556a2dd3f6012d6d0ae4abe20f0ebc1997e4ae13 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 28 Feb 2026 05:29:03 +0300 Subject: [PATCH 089/202] subghz: add ditec gol4 protocol by @xMasterX (MMX) & @zero-mega --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../main/subghz/helpers/subghz_gen_info.c | 9 + .../main/subghz/helpers/subghz_gen_info.h | 6 + .../helpers/subghz_txrx_create_protocol_key.c | 30 + .../helpers/subghz_txrx_create_protocol_key.h | 8 + .../subghz/scenes/subghz_scene_set_button.c | 5 + .../subghz/scenes/subghz_scene_set_counter.c | 16 + .../subghz/scenes/subghz_scene_set_seed.c | 2 + .../subghz/scenes/subghz_scene_set_serial.c | 9 + .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/ditec_gol4.c | 729 ++++++++++++++++++ lib/subghz/protocols/ditec_gol4.h | 109 +++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/public_api.h | 18 + targets/f7/api_symbols.csv | 1 + 16 files changed, 956 insertions(+) create mode 100644 lib/subghz/protocols/ditec_gol4.c create mode 100644 lib/subghz/protocols/ditec_gol4.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index 4242e552d..ebb120929 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -74,6 +74,7 @@ typedef enum { SetTypeKingGatesStylo4k, SetTypeBenincaARC, SetTypeJarolift, + SetTypeDitecGOL4, SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, diff --git a/applications/main/subghz/helpers/subghz_gen_info.c b/applications/main/subghz/helpers/subghz_gen_info.c index bce3d6856..db3f2926a 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.c +++ b/applications/main/subghz/helpers/subghz_gen_info.c @@ -559,6 +559,15 @@ void subghz_scene_set_type_fill_generation_infos(GenInfo* infos_dest, SetType ty .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, diff --git a/applications/main/subghz/helpers/subghz_gen_info.h b/applications/main/subghz/helpers/subghz_gen_info.h index 478039c45..60212801c 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -14,6 +14,7 @@ typedef enum { GenKingGatesStylo4k, GenBenincaARC, GenJarolift, + GenDitecGOL4, GenNiceFlorS, GenSecPlus1, GenSecPlus2, @@ -100,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_create_protocol_key.c b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c index 72197c0c9..11bb70ed9 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -455,6 +455,36 @@ bool subghz_txrx_gen_jarolift_protocol( 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, 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 9cd75fad7..811724584 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -140,6 +140,14 @@ bool subghz_txrx_gen_jarolift_protocol( 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/scenes/subghz_scene_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index 9f96f03d2..2006fdfa6 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_button.c +++ b/applications/main/subghz/scenes/subghz_scene_set_button.c @@ -52,6 +52,10 @@ void subghz_scene_set_button_on_enter(void* context) { 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); @@ -101,6 +105,7 @@ bool subghz_scene_set_button_on_event(void* context, SceneManagerEvent event) { case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: + case GenDitecGOL4: case GenNiceFlorS: case GenSomfyKeytis: case GenSecPlus2: diff --git a/applications/main/subghz/scenes/subghz_scene_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index d9ad3a42c..be13499b8 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -58,6 +58,10 @@ void subghz_scene_set_counter_on_enter(void* context) { 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); @@ -142,6 +146,9 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { 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; @@ -236,6 +243,15 @@ bool subghz_scene_set_counter_on_event(void* context, SceneManagerEvent event) { 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, diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index 1ba7d4c61..b3bd447e6 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -34,6 +34,7 @@ void subghz_scene_set_seed_on_enter(void* context) { case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: + case GenDitecGOL4: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: @@ -97,6 +98,7 @@ bool subghz_scene_set_seed_on_event(void* context, SceneManagerEvent event) { case GenKingGatesStylo4k: case GenBenincaARC: case GenJarolift: + case GenDitecGOL4: case GenNiceFlorS: case GenSecPlus2: case GenPhoenixV2: diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index 58fbbf026..ba105a804 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_serial.c +++ b/applications/main/subghz/scenes/subghz_scene_set_serial.c @@ -58,6 +58,10 @@ void subghz_scene_set_serial_on_enter(void* context) { 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); @@ -137,6 +141,10 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { 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); @@ -171,6 +179,7 @@ bool subghz_scene_set_serial_on_event(void* context, SceneManagerEvent event) { 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 e696664f5..7ee72ea21 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -24,6 +24,7 @@ static const char* submenu_names[SetTypeMAX] = { [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", @@ -227,6 +228,15 @@ bool subghz_scene_set_type_generate_protocol_from_infos(SubGhz* subghz) { 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, @@ -310,6 +320,7 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { 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/lib/subghz/protocols/ditec_gol4.c b/lib/subghz/protocols/ditec_gol4.c new file mode 100644 index 000000000..c7ca600fa --- /dev/null +++ b/lib/subghz/protocols/ditec_gol4.c @@ -0,0 +1,729 @@ +#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 = 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, +}; + +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_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 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; + } + } +} + +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; + } +} + +uint8_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderDitecGOL4* instance = context; + return subghz_protocol_blocks_get_hash_data( + &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..e45e1d73c --- /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 + */ +uint8_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/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 14d54b8d3..d4390fd7f 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -28,6 +28,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_feron, &subghz_protocol_roger, &subghz_protocol_elplast, &subghz_protocol_treadmill37, &subghz_protocol_beninca_arc, &subghz_protocol_jarolift, + &subghz_protocol_ditec_gol4, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 2c36260ed..a2067f4e8 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -56,3 +56,4 @@ #include "treadmill37.h" #include "beninca_arc.h" #include "jarolift.h" +#include "ditec_gol4.h" diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index 7539352dc..43bef118b 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -249,6 +249,24 @@ bool subghz_protocol_jarolift_create_data( 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( diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 136786c7d..bb03dcbb6 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3721,6 +3721,7 @@ 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* From 851303d191971bb5d9457f69242293a734152a29 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 28 Feb 2026 06:01:15 +0300 Subject: [PATCH 090/202] upd changelog --- CHANGELOG.md | 1 + documentation/SubGHzSupportedSystems.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e65641fad..9cbcc36fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 87.6 +* SubGHz: Add **Ditec GOL4** protocol (with programming mode, button switch, add manually) (by @xMasterX (MMX) & @zero-mega) * SubGHz: **BFT Mitto fix decode bug** (seed was not resetting after one successful decode) * SubGHz: **Somfy Keytis** button switch and **Add Manually support** * SubGHz: **KeeLoq** change delta size diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 69ec4d4ec..3284bc060 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -21,10 +21,11 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - Ansonic `433MHz` `FM` (12 bits, Static) - BETT `433.92MHz` `AM650` (18 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) +- BFT Mitto `433.92MHz` `AM650` (64 bits, Dynamic, KeeLoq based with Seed taken from serial) - CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) - CAME TWEE `433.92MHz` `AM650` (54 bits, Static) - 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) From 6085dc08426ae4b505a6466a6166a57eda9548b8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 28 Feb 2026 15:20:41 +0300 Subject: [PATCH 091/202] ditec fix missing parity bits --- lib/subghz/protocols/ditec_gol4.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/subghz/protocols/ditec_gol4.c b/lib/subghz/protocols/ditec_gol4.c index c7ca600fa..c046ae770 100644 --- a/lib/subghz/protocols/ditec_gol4.c +++ b/lib/subghz/protocols/ditec_gol4.c @@ -88,6 +88,14 @@ static uint8_t gol4_bit_reverse(uint8_t b) { 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; @@ -178,6 +186,9 @@ static void gol4_encode_bitrev_and_rotate(uint8_t* raw) { 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--) { @@ -186,6 +197,8 @@ static void gol4_encode_bitrev_and_rotate(uint8_t* raw) { 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) { From 1c1fe6d74e62482bd5d26da5c0eaa5196a55ce47 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 28 Feb 2026 22:16:02 +0100 Subject: [PATCH 092/202] FBT: Save 2KB flash on flipper_application --- firmware.scons | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware.scons b/firmware.scons index e53fd8edf..378b196a2 100644 --- a/firmware.scons +++ b/firmware.scons @@ -67,7 +67,7 @@ env = ENV.Clone( }, "flipper_application": { "CCFLAGS": [ - "-Og", + "-Os", ], "CPPDEFINES": [ "NDEBUG", From c98fa215da6d824462017c9bd2c4cd67be2adffb Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sat, 28 Feb 2026 23:41:19 +0100 Subject: [PATCH 093/202] FBT: Use LTO for firmware dfu --nobuild --- firmware.scons | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/firmware.scons b/firmware.scons index 378b196a2..bbd4a54a4 100644 --- a/firmware.scons +++ b/firmware.scons @@ -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()) From 3c1cbe39d07b90020abc00767bf6c6de8a5edb4f Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 01:28:12 +0100 Subject: [PATCH 094/202] Fix #include'd C files by mistake --- applications/main/subghz/scenes/subghz_scene_transmitter.c | 1 - targets/f7/furi_hal/furi_hal_bt.c | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_transmitter.c b/applications/main/subghz/scenes/subghz_scene_transmitter.c index f22cd3e24..16ba681c8 100644 --- a/applications/main/subghz/scenes/subghz_scene_transmitter.c +++ b/applications/main/subghz/scenes/subghz_scene_transmitter.c @@ -5,7 +5,6 @@ #include -#include #include "applications/main/subghz/helpers/subghz_txrx_i.h" #include "lib/subghz/blocks/generic.h" 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 From 0ac1b9b52dbdee802c3ec10aadace6f4cbc7cd00 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 01:28:52 +0100 Subject: [PATCH 095/202] Sub-GHz: Add function used by app to API --- lib/subghz/protocols/marantec.h | 8 -------- lib/subghz/protocols/public_api.h | 8 ++++++++ targets/f7/api_symbols.csv | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) 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/public_api.h b/lib/subghz/protocols/public_api.h index d9ee062d0..1a3c097fb 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -284,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/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c30ae0357..1634dff7e 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3740,6 +3740,7 @@ Function,+,subghz_protocol_jarolift_create_data,_Bool,"void*, FlipperFormat*, ui Function,+,subghz_protocol_keeloq_bft_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_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*" From 514f88983f08c742fefee174ccd4988731dd21ff Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 01:29:39 +0100 Subject: [PATCH 096/202] FBT: Allow apps to specify custom cflags --nobuild --- CHANGELOG.md | 1 + scripts/fbt/appmanifest.py | 1 + scripts/fbt_tools/fbt_extapps.py | 5 +++-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a646c655..03fb85a3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - 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: 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) ### Updated: 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): From a59ef9f05d3aa9813451476fb3d9373208bdc874 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 02:15:45 +0100 Subject: [PATCH 097/202] Sub-GHz: Move app out of firmware DFU (VERY WIP) --- applications/main/subghz/application.fam | 41 ++++++++++++------------ targets/f7/api_symbols.csv | 2 -- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index fab4ec3e8..e143217b4 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -1,39 +1,40 @@ 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=[ + "*.c", + "!subghz_fap.c", + "!helpers/subghz_gps.c", + "!helpers/minmea.c", + "!subghz_cli.c", + "!helpers/subghz_chat.c", + "!subghz_extended_freq.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", +# 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_gps", diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 1634dff7e..2bd11bd2a 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,, @@ -3614,7 +3613,6 @@ 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*" From 55de2b95f19fe919f515a39e7a7620a9fe6147c7 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 02:16:32 +0100 Subject: [PATCH 098/202] FBT: Disable LTO if Sub-GHz app is external --- firmware.scons | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/firmware.scons b/firmware.scons index bbd4a54a4..cb6e47e22 100644 --- a/firmware.scons +++ b/firmware.scons @@ -217,12 +217,12 @@ sources.extend( ) # Link-Time Optimization for firmware dfu only, we are desperately out of flash space -if ENV["COMPACT"]: - fwenv.Append( - CCFLAGS=[ - "-flto", - ], - ) +# if ENV["COMPACT"]: +# fwenv.Append( +# CCFLAGS=[ +# "-flto", +# ], +# ) # Debug # print(fwenv.Dump()) From dd24b59afc535f77d2f40aafae3a8f6d6cb220cd Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 02:21:14 +0100 Subject: [PATCH 099/202] Sub-GHz: Shave off 0.2kb from fap --- .../main/subghz/helpers/subghz_txrx.c | 2 +- .../subghz/scenes/subghz_scene_more_raw.c | 57 ++++++------------- .../subghz/scenes/subghz_scene_saved_menu.c | 11 +--- .../scenes/subghz_scene_signal_settings.c | 2 +- .../main/subghz/scenes/subghz_scene_start.c | 15 +---- applications/main/subghz/subghz.c | 2 +- .../main/subghz/views/subghz_read_raw.c | 11 ++-- 7 files changed, 29 insertions(+), 71 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index fde5e4b67..1da282793 100644 --- a/applications/main/subghz/helpers/subghz_txrx.c +++ b/applications/main/subghz/helpers/subghz_txrx.c @@ -122,7 +122,7 @@ uint8_t* //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. - const uint8_t tx_pa_table[TX_PATABLE_COUNT] = { + static const uint8_t tx_pa_table[TX_PATABLE_COUNT] = { 0, 0xC0, //12dBm 0xCD, //7dBm 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_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_signal_settings.c b/applications/main/subghz/scenes/subghz_scene_signal_settings.c index 9b582d2ee..f4635844f 100644 --- a/applications/main/subghz/scenes/subghz_scene_signal_settings.c +++ b/applications/main/subghz/scenes/subghz_scene_signal_settings.c @@ -51,7 +51,7 @@ 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}, diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 6046b2c6d..8c77eacd2 100644 --- a/applications/main/subghz/scenes/subghz_scene_start.c +++ b/applications/main/subghz/scenes/subghz_scene_start.c @@ -63,42 +63,29 @@ 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); 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/subghz.c b/applications/main/subghz/subghz.c index 3bdcbdb07..e6d32013e 100644 --- a/applications/main/subghz/subghz.c +++ b/applications/main/subghz/subghz.c @@ -64,7 +64,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"}, 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; From 8236079122d9a48681946567f30f60f9bdf834c8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:12:19 +0300 Subject: [PATCH 100/202] subghz keeloq fix anmotors and hcs101 display --- CHANGELOG.md | 1 + lib/subghz/protocols/keeloq.c | 66 +++++++++++++++++++++++++++-------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cbcc36fe..714c3e88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * SubGHz: **KeeLoq** change delta size * SubGHz: **Genius Echo/Bravo** add 2 buttons hold simulation (0xB btn code) * SubGHz: Signal **Settings Improvements** (PR #968 | by @Dmitry422) +* SubGHz: KeeLoq **fix display** of **AN-Motors** and **HCS101** keys * OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex) * Apps: Build tag (**22feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 159dbc62a..2bf173994 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1567,7 +1567,8 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output SubGhzProtocolDecoderKeeloq* instance = context; 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); @@ -1579,16 +1580,56 @@ 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) { - // push protocol data to global variable + // 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; + 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", + 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, "BFT") == 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; - - 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; - // ProgMode prog_mode = subghz_custom_btn_get_prog_mode(); if(prog_mode == PROG_MODE_KEELOQ_BFT) { @@ -1629,10 +1670,9 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output instance->generic.seed); } } else if(strcmp(instance->manufacture_name, "Unknown") == 0) { - 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; + // 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" @@ -1649,12 +1689,10 @@ 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; - 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" From 1854c9d19081e03952ca894448679d623e10bab0 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 22:36:17 +0100 Subject: [PATCH 101/202] Update apps --nobuild --- CHANGELOG.md | 19 ++++++++++++++----- applications/external | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03fb85a3f..0ccc28a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,18 +25,27 @@ ### 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) - 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.10.2 (by @justcallmekoko) + - ESP32 WiFi Marauder: 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 (by @H4W9) + - Flipper Blackhat: TUI command (by @o7-machinehum) - Geometry Dash: Major refactor, bugfixes and performance improvements, rename from Geometry Flip (by @gooseprjkt) - 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) + - INA2xx INA Meter: Fixed application freezing when the sensor is not connected (by @cepetr) + - NFC Login: Code refactor, bugfixes, renamed from NFC PC Login, restore BLE profile on app exit (by @Play2BReal) + - Picopass: Option to Create credential without a card (by @redteamlife) - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse) - - Seos Compatible: Seos write support, various improvements (by @aaronjamt) + - 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) + - Tetris: Various bugfixes (by @Bricktech2000) - Unitemp: Numerous improvements from @MLAB-project fork (by @MLAB-project) - - UL: Update Sub-GHz apps for FM12K modulation (by @xMasterX) + - XRemote: Add dolphin xp and crash bugfix (by @teohumeau) - Sub-GHz: - UL: Counter editor refactoring (by @Dmitry422) - UL: Alutech AT-4N & Nice Flor S turbo speedup (by @Dmitry422) diff --git a/applications/external b/applications/external index e9230bc08..9b2d6a5b1 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit e9230bc089ae26094a9cba4614815c36532fc964 +Subproject commit 9b2d6a5b1b9726105868b250ae3c65af5ac229f0 From ccc621ad581498b51b47cb2c23c67a35a382415e Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 23:04:11 +0100 Subject: [PATCH 102/202] Apps: Add UL-C Bruteforce, UL-C Relay. ULCFkey (by noproto) --nobuild --- CHANGELOG.md | 6 +++++- applications/external | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ce989538..b49924038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ ### Added: - Apps: - - NFC: ISO 15693-3 NFC Writer (by @ch4istO) + - 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.) - Sub-GHz: - 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) diff --git a/applications/external b/applications/external index 9b2d6a5b1..f71049f74 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 9b2d6a5b1b9726105868b250ae3c65af5ac229f0 +Subproject commit f71049f74a390e2934f30ca31579cbf25a628847 From 1b61a39a3757fc8455920cbcb1f0f0fe92aaa517 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 1 Mar 2026 23:27:16 +0100 Subject: [PATCH 103/202] Apps: Add Flipper Wedge (by dangerous-tac0s) --nobuild --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b49924038..16fadee0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - 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: 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) diff --git a/applications/external b/applications/external index f71049f74..850a3234d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit f71049f74a390e2934f30ca31579cbf25a628847 +Subproject commit 850a3234d9a7596b4e21951733e8e04b6e2afa7c From 7a614a368391de0284ef52e916839ed6b0f6e7a8 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 2 Mar 2026 00:33:23 +0100 Subject: [PATCH 104/202] Apps: Add Checkers (by H4W9) --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16fadee0f..ac9cbf8c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ### Added: - Apps: + - Games: Checkers (by @H4W9) - NFC: - ISO 15693-3 NFC Writer (by @ch4istO) - UL-C Bruteforce (by @noproto) diff --git a/applications/external b/applications/external index 850a3234d..8c980b149 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 850a3234d9a7596b4e21951733e8e04b6e2afa7c +Subproject commit 8c980b1498ecfdfe9995e1d39b644235680ecc47 From d18619ff5283d3874e1c6cc0b08d3504fc00ab11 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 5 Mar 2026 05:07:58 +0300 Subject: [PATCH 105/202] subghz add keyfinder 24b protocol --- CHANGELOG.md | 3 +- documentation/SubGHzSupportedSystems.md | 1 + lib/subghz/protocols/keyfinder.c | 363 ++++++++++++++++++++++++ lib/subghz/protocols/keyfinder.h | 109 +++++++ lib/subghz/protocols/protocol_items.c | 2 +- lib/subghz/protocols/protocol_items.h | 1 + 6 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 lib/subghz/protocols/keyfinder.c create mode 100644 lib/subghz/protocols/keyfinder.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 714c3e88f..f024b31ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main changes - Current API: 87.6 -* SubGHz: Add **Ditec GOL4** protocol (with programming mode, button switch, add manually) (by @xMasterX (MMX) & @zero-mega) +* SubGHz: Add **Ditec GOL4** 54bit dynamic protocol (with programming mode, button switch, add manually) (by @xMasterX (MMX) & @zero-mega) +* SubGHz: Add **KeyFinder** 24bit static protocol (thx @mishamyte for RAWs) * SubGHz: **BFT Mitto fix decode bug** (seed was not resetting after one successful decode) * SubGHz: **Somfy Keytis** button switch and **Add Manually support** * SubGHz: **KeeLoq** change delta size diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 3284bc060..12f409eb4 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -82,6 +82,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) --- 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/protocol_items.c b/lib/subghz/protocols/protocol_items.c index d4390fd7f..8dfe647b1 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -28,7 +28,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &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_ditec_gol4, &subghz_protocol_keyfinder, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index a2067f4e8..a0214b74f 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -57,3 +57,4 @@ #include "beninca_arc.h" #include "jarolift.h" #include "ditec_gol4.h" +#include "keyfinder.h" From f7da751d5f360845d59c1bf70ed1f1b7a744f07b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 7 Mar 2026 23:50:06 +0300 Subject: [PATCH 106/202] Make FDX-B readout more descriptive OFW PR 4297 by snowsign --- lib/lfrfid/protocols/protocol_fdx_b.c | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) 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); } From aa75974395cd37b334de4f46b4f2884e61f22ce7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 7 Mar 2026 23:53:22 +0300 Subject: [PATCH 107/202] feat: add Indala 224-bit (long format) RFID protocol support OFW PR 4343 by kuzaxak --- .../tests/lfrfid/lfrfid_protocols.c | 131 ++++++++ lib/lfrfid/protocols/lfrfid_protocols.c | 2 + lib/lfrfid/protocols/lfrfid_protocols.h | 1 + lib/lfrfid/protocols/protocol_indala224.c | 281 ++++++++++++++++++ lib/lfrfid/protocols/protocol_indala224.h | 4 + 5 files changed, 419 insertions(+) create mode 100644 lib/lfrfid/protocols/protocol_indala224.c create mode 100644 lib/lfrfid/protocols/protocol_indala224.h 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/lib/lfrfid/protocols/lfrfid_protocols.c b/lib/lfrfid/protocols/lfrfid_protocols.c index 22533c4a0..df88f27bb 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" @@ -30,6 +31,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 616e1c807..60d0dd29c 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_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; From fa72ca8c7e508db366ca196ee6ebc1c1287b81c0 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:16:25 +0300 Subject: [PATCH 108/202] upd changelog --- CHANGELOG.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f024b31ee..d753f6ca2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,13 @@ * SubGHz: **Genius Echo/Bravo** add 2 buttons hold simulation (0xB btn code) * SubGHz: Signal **Settings Improvements** (PR #968 | by @Dmitry422) * SubGHz: KeeLoq **fix display** of **AN-Motors** and **HCS101** keys -* OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex) +* OFW PR 4343: LFRFID: add Indala 224-bit (long format) protocol support (by @kuzaxak) +* OFW PR 4297: LFRFID: Make FDX-B readout more descriptive (by @snowsign) +* MFKey: Update to v4.1 (by @noproto & @dchristle) * Apps: Build tag (**22feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 4320: API: Make view_port_send_to_back public (by @loftyinclination) -* MFKey: Update to v4.1 (by @noproto & @dchristle) +* OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex)

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 6d68b37b536755c39e0c1c049f1137763b64cbfb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:17:26 +0300 Subject: [PATCH 109/202] subghz fix very big issue with tx on read screen --- CHANGELOG.md | 1 + .../subghz/scenes/subghz_scene_receiver_info.c | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d753f6ca2..6956a8e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Current API: 87.6 * SubGHz: Add **Ditec GOL4** 54bit dynamic protocol (with programming mode, button switch, add manually) (by @xMasterX (MMX) & @zero-mega) * SubGHz: Add **KeyFinder** 24bit static protocol (thx @mishamyte for RAWs) +* SubGHz: **Fix transmission bug on Read screen** * SubGHz: **BFT Mitto fix decode bug** (seed was not resetting after one successful decode) * SubGHz: **Somfy Keytis** button switch and **Add Manually support** * SubGHz: **KeeLoq** change delta size diff --git a/applications/main/subghz/scenes/subghz_scene_receiver_info.c b/applications/main/subghz/scenes/subghz_scene_receiver_info.c index 1d1f827f6..193337079 100644 --- a/applications/main/subghz/scenes/subghz_scene_receiver_info.c +++ b/applications/main/subghz/scenes/subghz_scene_receiver_info.c @@ -133,19 +133,19 @@ 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 { - // 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; + subghz_block_generic_global.endless_tx = false; } return true; } else if(event.event == SubGhzCustomEventSceneReceiverInfoTxStop) { From bafd4e89c93d85585b7dbb9ff8d066283e9a1023 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 00:43:08 +0300 Subject: [PATCH 110/202] upd changelog apps sync --- CHANGELOG.md | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6956a8e5d..aca368515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,8 @@ ## Main changes - Current API: 87.6 -* SubGHz: Add **Ditec GOL4** 54bit dynamic protocol (with programming mode, button switch, add manually) (by @xMasterX (MMX) & @zero-mega) -* SubGHz: Add **KeyFinder** 24bit static protocol (thx @mishamyte for RAWs) -* SubGHz: **Fix transmission bug on Read screen** -* SubGHz: **BFT Mitto fix decode bug** (seed was not resetting after one successful decode) -* SubGHz: **Somfy Keytis** button switch and **Add Manually support** -* SubGHz: **KeeLoq** change delta size -* SubGHz: **Genius Echo/Bravo** add 2 buttons hold simulation (0xB btn code) -* SubGHz: Signal **Settings Improvements** (PR #968 | by @Dmitry422) -* SubGHz: KeeLoq **fix display** of **AN-Motors** and **HCS101** keys -* OFW PR 4343: LFRFID: add Indala 224-bit (long format) protocol support (by @kuzaxak) -* OFW PR 4297: LFRFID: Make FDX-B readout more descriptive (by @snowsign) -* MFKey: Update to v4.1 (by @noproto & @dchristle) -* Apps: Build tag (**22feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**9mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* OFW PR 4320: API: Make view_port_send_to_back public (by @loftyinclination) -* OFW PR 4338: HID: Fix USB HID keyboard LED state reporting (by @Caballosanex) +* None yet

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 6553b08820ca8609a9baf86a485fc441727d0fcc Mon Sep 17 00:00:00 2001 From: haw8411 <109001610+haw8411@users.noreply.github.com> Date: Sun, 8 Mar 2026 19:22:09 -0400 Subject: [PATCH 111/202] NFC: Add Mifare Ultralight C Write Support (#524) * Updated Ultralight C Poller & Other Files to support Ultralight C Write * Delete ulcwrite.patch * Removed Cache, fixed nits, and fixed clang-format * Remove dead target_uid fields (cache removed) * Fix issues WillyJL suggested * Fix aaronjamt's code suggestions * Fix malloc suggestion * Remove furi_log * Update changelog --------- Co-authored-by: haw8411 Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../mf_ultralight/mf_ultralight.c | 171 +++++++++++++++++- applications/main/nfc/nfc_app_i.h | 13 ++ .../nfc_scene_mf_ultralight_c_dict_attack.c | 12 ++ .../protocols/mf_ultralight/mf_ultralight.c | 6 +- .../mf_ultralight/mf_ultralight_listener.c | 22 ++- .../mf_ultralight/mf_ultralight_poller.c | 115 +++++++++++- .../mf_ultralight/mf_ultralight_poller.h | 2 + .../mf_ultralight/mf_ultralight_poller_i.h | 2 + 9 files changed, 328 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4887fe580..c28e6d687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - 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) +- NFC: Add Mifare Ultralight C Write Support (#524 by @haw8411) - OFW: 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) 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/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/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/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..d8c5fb1e6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -186,7 +186,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 +197,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; From 4c06e9a7a45a9460c24155dc94002b76abe8f310 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 9 Mar 2026 00:23:09 +0100 Subject: [PATCH 112/202] Update apps --- CHANGELOG.md | 6 ++++-- applications/external | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c28e6d687..4e1eb6eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,14 +42,16 @@ - 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 (by @H4W9) + - Free Roam: Minimap, code optimization, ux improvements (by @jblanked) - Flipper Blackhat: TUI command (by @o7-machinehum) - Geometry Dash: Major refactor, bugfixes and performance improvements, rename from Geometry Flip (by @gooseprjkt) - 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) - INA2xx INA Meter: Fixed application freezing when the sensor is not connected (by @cepetr) + - Lidar Emulator: Support external IR boards (by @ANTodorov) - NFC Login: Code refactor, bugfixes, renamed from NFC PC Login, restore BLE profile on app exit (by @Play2BReal) - - Picopass: Option to Create credential without a card (by @redteamlife) - - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse) + - Picopass: Option to Create credential without a card (by @redteamlife), info screen improvements, allow Emulation of NRMAC dumped cards (by @bettse) + - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse), reverse engineered U90 packet, memory optimization (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) - Tetris: Various bugfixes (by @Bricktech2000) diff --git a/applications/external b/applications/external index 8c980b149..e5e6c914b 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 8c980b1498ecfdfe9995e1d39b644235680ecc47 +Subproject commit e5e6c914b8dccaa62a994da6af36e577697cbe4e From 430a3d506ea8c800e66d8b2f3d243631aebbe35c Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 9 Mar 2026 00:23:39 +0100 Subject: [PATCH 113/202] Apps: Add CAN Commander (by MatthewKuKanich) --- CHANGELOG.md | 1 + applications/external | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e1eb6eaa..bab67b77d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ### Added: - Apps: - Games: Checkers (by @H4W9) + - GPIO: CAN Commander (by @MatthewKuKanich) - NFC: - ISO 15693-3 NFC Writer (by @ch4istO) - UL-C Bruteforce (by @noproto) diff --git a/applications/external b/applications/external index e5e6c914b..805c3e7fd 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit e5e6c914b8dccaa62a994da6af36e577697cbe4e +Subproject commit 805c3e7fda279ac41e06b1c82e127db0f99c32b6 From 834e338273b5b391d772f3b13c59ffa97b2b16a7 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:28:08 +0300 Subject: [PATCH 114/202] Extend lcd contrast range to full ST756x 6-bit range by ShaTie --- .../notification_settings_app.c | 45 +++++-------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index e82b02c87..bb4563227 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -24,44 +24,19 @@ static const NotificationSequence sequence_note_c = { NULL, }; -#define CONTRAST_COUNT 17 +#define CONTRAST_COUNT 64 const char* const contrast_text[CONTRAST_COUNT] = { - "-8", - "-7", - "-6", - "-5", - "-4", - "-3", - "-2", - "-1", - "0", - "+1", - "+2", - "+3", - "+4", - "+5", - "+6", - "+7", - "+8", + "-32", "-31", "-30", "-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", + "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-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", "+19", + "+20", "+21", "+22", "+23", "+24", "+25", "+26", "+27", "+28", "+29", "+30", "+31", }; const int32_t contrast_value[CONTRAST_COUNT] = { - -8, - -7, - -6, - -5, - -4, - -3, - -2, - -1, - 0, - 1, - 2, - 3, - 4, - 5, - 6, - 7, - 8, + -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, + -16, -15, -14, -13, -12, -11, -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, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, }; #define BACKLIGHT_COUNT 21 From 9041334b425291b73913497890787303ca00976f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:36:40 +0300 Subject: [PATCH 115/202] NFC: Add Mifare Ultralight C Write Support by haw8411 --- .../mf_ultralight/mf_ultralight.c | 171 +++++++++++++++++- applications/main/nfc/nfc_app_i.h | 13 ++ .../nfc_scene_mf_ultralight_c_dict_attack.c | 12 ++ .../protocols/mf_ultralight/mf_ultralight.c | 6 +- .../mf_ultralight/mf_ultralight_listener.c | 22 ++- .../mf_ultralight/mf_ultralight_poller.c | 118 +++++++++++- .../mf_ultralight/mf_ultralight_poller.h | 2 + .../mf_ultralight/mf_ultralight_poller_i.h | 2 + 8 files changed, 329 insertions(+), 17 deletions(-) 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/nfc_app_i.h b/applications/main/nfc/nfc_app_i.h index 0e2c70942..f1113144d 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/scenes/nfc_scene_mf_ultralight_c_dict_attack.c b/applications/main/nfc/scenes/nfc_scene_mf_ultralight_c_dict_attack.c index b1e24def2..6eb1cfc42 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/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..d8c5fb1e6 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c @@ -186,7 +186,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 +197,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 e29e1cb6b..6dd761af8 100644 --- a/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c +++ b/lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.c @@ -456,7 +456,8 @@ static NfcCommand mf_ultralight_poller_handler_auth_ultralight_c(MfUltralightPol if(instance->mfu_event.data->key_request_data.key_provided) { instance->auth_context.tdes_key = instance->mfu_event.data->key_request_data.key; } else if(instance->mode == MfUltralightPollerModeDictAttack) { - // TODO: Can logic be rearranged to request this key before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? + // TODO: -nofl Can logic be rearranged to request this key + // before reaching mf_ultralight_poller_handler_auth_ultralight_c in poller? FURI_LOG_D(TAG, "No initial key provided, requesting key from dictionary"); // Trigger dictionary key request instance->mfu_event.type = MfUltralightPollerEventTypeRequestKey; @@ -697,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 { @@ -737,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); @@ -751,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; From 7239c42d25fc4125afe89f0c2b1caf4c18a0fa6e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 02:55:07 +0300 Subject: [PATCH 116/202] upd changelog --- CHANGELOG.md | 5 +++-- .../notification_settings_app.c | 16 ++++++---------- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aca368515..b70783b32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ ## Main changes - Current API: 87.6 -* Apps: Build tag (**9mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* NFC: Add Mifare Ultralight C Write Support (by @haw8411) +* Apps: Build tag (**9mar2026p2**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* None yet +* Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) diff --git a/applications/settings/notification_settings/notification_settings_app.c b/applications/settings/notification_settings/notification_settings_app.c index bb4563227..33a16c13e 100644 --- a/applications/settings/notification_settings/notification_settings_app.c +++ b/applications/settings/notification_settings/notification_settings_app.c @@ -24,19 +24,15 @@ static const NotificationSequence sequence_note_c = { NULL, }; -#define CONTRAST_COUNT 64 +#define CONTRAST_COUNT 29 const char* const contrast_text[CONTRAST_COUNT] = { - "-32", "-31", "-30", "-29", "-28", "-27", "-26", "-25", "-24", "-23", "-22", "-21", "-20", - "-19", "-18", "-17", "-16", "-15", "-14", "-13", "-12", "-11", "-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", "+19", - "+20", "+21", "+22", "+23", "+24", "+25", "+26", "+27", "+28", "+29", "+30", "+31", + "-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] = { - -32, -31, -30, -29, -28, -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, - -16, -15, -14, -13, -12, -11, -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, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + -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 From d8568f3e8b4f494ba9418a3bbebc8cbb093df1ac Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 03:37:10 +0300 Subject: [PATCH 117/202] upd changelog sync apps --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b70783b32..cdfa1d12d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ ## Main changes - Current API: 87.6 * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**9mar2026p2**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**9mar2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

From 04d7b46ce9eaca1d70d35347c27283e73f523735 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 18:15:14 +0300 Subject: [PATCH 118/202] add came twee support for TOP44FGN --- CHANGELOG.md | 1 + documentation/SubGHzSupportedSystems.md | 2 +- lib/subghz/protocols/came_twee.c | 10 +++++++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfa1d12d..530fd147f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 87.6 +* SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * NFC: Add Mifare Ultralight C Write Support (by @haw8411) * Apps: Build tag (**9mar2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 12f409eb4..5b6e427c8 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -23,7 +23,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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) - CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) -- CAME TWEE `433.92MHz` `AM650` (54 bits, Static) +- 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) diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index d6b656a57..55947efbf 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -322,9 +322,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; From a5f47e3e6d162550b4a2cbdb958195e69a923730 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 9 Mar 2026 20:52:18 +0300 Subject: [PATCH 119/202] subghz add nord ice protocol --- CHANGELOG.md | 1 + documentation/SubGHzSupportedSystems.md | 3 +- lib/subghz/protocols/nord_ice.c | 350 ++++++++++++++++++++++++ lib/subghz/protocols/nord_ice.h | 109 ++++++++ lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + 6 files changed, 464 insertions(+), 1 deletion(-) create mode 100644 lib/subghz/protocols/nord_ice.c create mode 100644 lib/subghz/protocols/nord_ice.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 530fd147f..e73a1a45b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## Main changes - Current API: 87.6 +* SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * NFC: Add Mifare Ultralight C Write Support (by @haw8411) * Apps: Build tag (**9mar2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 5b6e427c8..8c552e9c7 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -41,6 +41,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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) @@ -106,7 +107,7 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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 ECHO, Genius Bravo (Button code 0xB for prog. mode)) - Gibidi - `433.92MHz` `AM650` (KeeLoq, 64 bits) diff --git a/lib/subghz/protocols/nord_ice.c b/lib/subghz/protocols/nord_ice.c new file mode 100644 index 000000000..2e7d0caa9 --- /dev/null +++ b/lib/subghz/protocols/nord_ice.c @@ -0,0 +1,350 @@ +#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 = 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, +}; + +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; + } +} + +uint8_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( + &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..4149fba7d --- /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 + */ +uint8_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/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 8dfe647b1..416e3524f 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -29,6 +29,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_elplast, &subghz_protocol_treadmill37, &subghz_protocol_beninca_arc, &subghz_protocol_jarolift, &subghz_protocol_ditec_gol4, &subghz_protocol_keyfinder, + &subghz_protocol_nord_ice, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index a0214b74f..d9e5f0cd0 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -58,3 +58,4 @@ #include "jarolift.h" #include "ditec_gol4.h" #include "keyfinder.h" +#include "nord_ice.h" From 648c031496a82ad447643815791fa1b891d3c2e8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 10 Mar 2026 02:45:07 +0300 Subject: [PATCH 120/202] upd changelog sync apps --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e73a1a45b..40b8982d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**9mar2026p3**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**10mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

From 11cb99168ef62b35b5a35d3886b8e2098027f3eb Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 12 Mar 2026 21:24:10 +0300 Subject: [PATCH 121/202] sync apps --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b8982d8..cda75a330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**10mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**12mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

From 70c2b3c6270dbfc2f05f8b79887b506b279e0c9b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:43:36 +0300 Subject: [PATCH 122/202] add zero and ffff mfkeys --- .../resources/subghz/assets/keeloq_mfcodes | 142 +++++++++--------- 1 file changed, 73 insertions(+), 69 deletions(-) diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index ae6315a2d..572bda5cc 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,72 +1,76 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 6E 6F 74 69 63 65 73 20 62 75 6C 67 65 4F 77 4F -212F687B2D38E6E9066E95894E455A6AD3A8861CDDAFD09DBC2557506676332D -2CFF25E9743E4588820D24998B5047E8019C6E200922BCB66830C7F722822A79 -74AA0EF345C27F583042C4EDDBB83D5446387D4E5B31DFD3F7B9D09F0662A0AA95BEB40BF37678954CAE0200B2898D22 -6998443F71D17938425CD231B8500E769976601315D79D673AA37E4F9BDCE50B -3335691566FEDDDE5828BE0A5BBBD9AC013987A1134E68020D3F0F477E09BC71 -1B3A3CBB3D56783220BEAECEA15FFF5CD92AF44F63547F6F84F4533D2B3D820B -A1ECFE74E714D0E1514D0C18903598E7FB7E7AF5C0165DEDE85DFAE05B26C0EC -BA3F2EFBE7CB6671206EA90DA903FF48CCABEC9F393D55B9DB3B7E207A48C059 -CA91805C8ADF02501B41D6BF0C69D7C3DD6E218D4B4EB6BED2FDE1367256E544 -5465E78D877F2594AF40115723C8AC71D09F60ACCC8121CD3B6BA271BEA43B5C -BB1D3DE0A048694FC86CA8E79BB7BFBB1EF91515C44E917FD5017676EE5C4B13 -48C08D8B2B42D025D2557CC650EDA3C5CC301CD71169CFE0044C07A95224109B -3DAE6C5B94C237FF10CF82FAA58517DB4DD4E4D82FD81CFAAA4836C9B8460CE1 -645F8F5D213889BAB0F437497856A0CF402E3D4A3679258BB0BF2B768D1F1714 -AFC3C462698C795B1A407F27F499CD1757931BD676BA43E4B41E8EF2672EC4AE -5A19F0D1CD87C40883D7BE51377CDAD94C977B4F6CFCCECED91D56867C9EC211 -716645BD4F5B18002417A92D6A2BD29D18087F7ADB549A90A2661EB4163EF190 -54B83F7F7F50AD4C3DC3498822240AEB7D9A43728704F14691A39C79FAC9B6A3 -FFDDCEFAFF3ADA3616B388684B1AD4987B83E459BA5037E377FE17CC7EDAA430 -9CC98590A371E580B164A5F1369945A6DB653797331EC898755E84BE93EDF3EF -63697AD15064194A3986CC1F3111A09E3D86AC8D370A8EC4D22EDDA4B9661B63 -F7CE4FA63EA9D388AE4AB6A468768AE91E2E3CF6F1D9A5EA17845466A6801A8C -38B2DE32CF36D68842CB31FD97394892B76FB4E81FCFB33164676BAB354939DE -93192FA1D7E347AF9FC98EE49A8B02869527511D0B263FD3B08CCFE645A39F51 -3D5F832CF75D94B58B078B4EE0DFC1D987795CD49959517F391F9E9F09CA187C -6CBCCCE0F8E6A833D5CA18E09E821A7F93B9FD7D912632E277B1721985F4B701 -5896050E9F4950CD7061ED4DD3BA9FC6013CA52F5C1B0493110FA3934116AC66 -F0D3FF283FBD7CFBFE697DBE752EDD5A1F124DCCAE3B629A146E0E85A7ED4E06 -6B6C7C365A5CABA2F1023ED1F97D182F0B9D575C64393272E19B8B809DF3C329 -1AF30761D85AE1C6DD4D78D4ECE79983C7F58ECE859709570262811CFCA8C97F -FC86201D96F4AE36C63A1C00DA34AC14BFDE317383D19A534362E6149E860366 -54F55F34A27E1D690F56269D29C795105C8407A4E1A9ED431588F559948C115A -4DD62ECD2FE8CBB783E3D7F0B1D95496D585405DE5AEB07C8A4E053B3AAAD808 -EDB356AFA869EFB987F099239449D6B9F1469EA6D8C9F0A6B4171F685E2853A1 -03C058A2C0DB83179E520F035BBAAB01673EFE4FDE717A0F5D1AB6B81BC9D033 -D2F33C08136AB161EED6121E721F8C479CED5F26A1B7DEEDEA8580314C232C5E -51CB3275D6D7250CD88A446242590C3DFD36DF83F5E34379DE73601511CD24B9 -ED4EC928AC505CBEFA6ACB2901C8E2221FC5073624B0B78F8365DD0442EFBA10 -76F959BBF8A4EC4AC22F41F06719BCBA58F070CB65B282BFC2C99F6CC02E30DB -0A44A7C5D4ECF2F08CBDF3FE947D45C6AC586D4558DBFB36719927F58EBB525B -F096777DDC5BF671CBF1A9682EBEE8308AA1002F6CB6899D575A30CFACB30125C8378864C77922360ADD0AC486EFF7F4 -372863D5B6F6D7A671AB734A99403AF230DEBBBA435AB8F236B179960FAFDFC6 -6CC31C5E86CF6851D92FA973253B3B1FC9023B150980A216A63720CD74592EB7 -6643B2CA94415FE4A4E25A3086B8370172C53283360A7BD54B322BD889A5A831 -03209037BD7E96A16C69A4F0B6FEB3F2ABA1408353BE09C53485CB7C2362A490 -BE4619EC34F7FB8DFE53A8ABFDE6A831895B88D0AB1A9B4861BC7EC7F1121611 -41223F7AD8B0F28D08C1EEC2B96324F337F6428759032CAE58A04E8511BC4CBCE497EE1502F18BED5B5038E719F4F085 -1533D9B78F16803DFD8AC0C9C85ECEB6BABE9ADF83BF0B6692857BAA03843444064FCDBC89464D9E002AAFF53C65098B -466CC36F82D1AF9BA0704789B2F8B249E79D4964E7E5CB74917760E7D30AB7FA -E9673C0CF35137C179132DDCA57869E285FE6014BB32C5DD78E7549ED0076906 -A511A9999C777750FA348772A9E2881A75DEC94ED833AC282037B3E10913B150 -F20652B2CA0E0ADD34331D2462CD5AFACC4E01F24EF17C97CFF57089039AD7D8 -AFCB6518894626B9E8AFC7E7D9D0430D1D820969CE79B61B91E63D08D4995308D8403705AE4E25AB0F254D47FB03A1A2 -C43987548689A1DD9A65A3BE76F97A8470263ACAC0696B5882383AE97893B5D6 -7FB47AC9A16094C38D436F9932041A40F6B4644A633361B47C6F6E4A8F2E7C26 -BDD48C4689912872055999ECADCF93D152ACDEC34C6318F2FBBBA1A34C8CC166FCBA31AED11F95C973B21A646828CEEA -1582E265DF46C8A9A019AF338104C267873FD3C2C7AF290263FEF921CC9926E76F37FA31881307C675CF37F8A5A236D7 -790D8AF6D9F5B634F953F5751378BA5412425B950C696549902EB6A9383630D0 -6F44FF0DB55651218C81B41153C9D77B1911C5BFA70650FB9326DD6B8A1479EE -4B0891C1C24E004AB2869A858941C47346EF0789DFAEA1CD1B0E008A73AF2F7E -7E9F990652FD6632612866B6ECA726EAE2112234EBCA59D0C1BF220B94E1F472 -9C08E9DAE977DD30E61E6634F51BFD654330C34E9FB3AA2E8856B428141D7164 -7C0E756C593FFBBE2609F63D142A0D53F99772EB0722616DFC7B203BA70AA5AE -52C03D8984496DFF95503D843CA1980A6CA46AAE3390C6D4D38E277A0937429C185C118B98B6EBE0711F10F47F8D8F3F -DE8A5BFD2AB351BCAD1D7817737946EF2445E3C974C9E11BF7AD058463F0E437 -D09ABB320F59A75C4B2EE8B52E45D52C46226DD8BC8339F53229578733928F1F -C2E6F135E638C83363097EB79ED9A58E94A8E5F626E27083D1363E8BC61B55A4 -F06CC55446D0FCCCE5AD007949AE3FCA9C6FCB3C0CA1B5DE1BA65544B55326F1CF726E1F1E5CA90CA2B986930C9B9D83 +IV: 4E 6F 74 69 63 65 73 20 62 75 6C 67 65 55 77 55 +DD5D658073C2C257430FD30815480D300A4FAA72750B741431D90E7B74C6F94B +D30DBA9B63C4A5A47E6F1462819706F347B4524FB0342729B454791ECB491338 +4BF3E836BECF53BF8ECADC9E35EB94A6F2A424C4918226B7B2F3B0102BB28AFB61610BF188269855559DDEF49413635B +96BD4E3E3D02094DFE3C4E5CCBC8ED7BE679ACE3C83ADF1433BA4E08576E610D +99B4D5475099E999558BBBCFC5083B08BB99FF7F9E8EFEB222388750CBA464CD +DA94EF9FED763E6625C2FA839C8A288986664EFD12575D21575B6C77345EC6A2 +FD0A76366C8F84C2BF080A40EB6573CF41AFA51138320C187BDDB1E40CEA35E8 +5C375767AB4BE93E3A10875B4B006D4264FD9FB606381A8878AF4EC960EFC849 +01297BF2D4372C5FC376E79835D8D5A382F76E3D9367E2FA81F54C10BDB2FDA8 +415FE673D1CDAE0E7141EA78326573987E51353C91992E334FCD030E3172B8C6 +C6EA321FA06DBD86D328B5CBBCFE94628DA3D415463835229F1224EAB5F8B181 +6468B83FB26298BD496299D64DAE0F2D12B081CDE9B83AA5B19500162D7DA6E8 +2569E458EFFE0725581F7490CBF416B3E8E64C1D0F2C13DEA5BE926475F631DC +857A04B029277DA403276CADE88EAD4956A959AB9C652614F435DCE1B3B92215 +A08B60A323178B3A8644D0018C705E5E13F5F34DB41132348CEDBBCB6C75164F +A171F6CEEB240D1117DF8A5396B4E468EB03D8AA315321FA19F01E67145A861E +4255FAC9CD843A206D89D6D5204409193E594AE73D5E490F84F39DC76A0C6C23 +589E6270609485D8FEA3029A23C3084F48FB5384939ACF447CA289C4282F38BA +D1B349CF3C402BCF7F87FEF5A19E8C2748D52715092EC5668C3C61A15958F7F2 +34191142584299F22C0A007EC8E333646E57C6B406C103795ED4A2731CAF55A5 +158809D80C052D780C8AA05F88AF67FF4CF93234A7F7A78B5AA11F067623AD2D +81C22401A96674BB64D3512AEEB5F8A060537AFE1D4F2BB31ADB237E1BC61FC0 +C732F26FEC449F545CD379BF9AD2AC2769EDA98C0D7163A69E37EE17853E98AF +59375DCD3703B87223DE49174274CD28FF95BA077357A10304FC3B44E679CA91 +E9E9E87E1098CCB6C3799EB31D91E9C77EF243164FE98DAA688B22CD8ED7D944 +AC0319E2F9D0520357E61B88B17D293AE32B68661484890AEBCBADA0E7C47C40 +865F354CA28D9AABFFB3ABBD15CB22ECBDC937D292AA2D72B9D7457E870E9117 +7D2D90D3D583F77F60C8782E61011D52A452FC33F8B5E6A8AD6A2378F2819D3B +FC3C20DA524C0E1D43881F15605E18B58D9E804D15F01AB3DC9850D472C4C3A1 +25C03EAA292F03155ACD4C599030C159C2F085970B2BD905A6CF42E69D5E7BF4 +F84099879C1BD93BD77489CBEE9DB22F7F4688480A5DB9F1CF8359ADAA456B10 +21A4BE7E01B1848FB26E7CFEAD9FA841F4B80C4C2C84D07F6737F61E1FB4F4F3 +8094BD23328B5854C11FE696D21F1F58C280DC6D31F7F6B56F99E04D83949EFD +B87664A507982A6103904E81BF7D6AE1222E038D405EE84F3B5E84F4E3D6BFC7 +F102B622433FCBA3910DA43309D67FC46BE7A472BAAF8CDDEFFCD796D23DB122 +D3A015CE7E7D9CD23CED808CC72CBB42A09C5D718B58C04E1E25B3D7F46BCBB3 +02E1C8124D6B955689193369C87BDFCC66C0750E65EED7D7592E6EECA83BF1CA +4A37AC36B48DF89D664CC0390F2B1AAF553EBC2C661D14E5DDA2016C66946FF6 +3087AB22BF5A72376A888C26846B15DF842E70F8AE113E64BC855B2BA4801831 +562ED92C172D0B20799D0FDC565AED5B2A771249ACBA7D8F33176B99B3C4CD88 +448D6DEAC1C01D1B96EBB480D22A5AD011DC36655D8318BACCEE630F9BA5CC20 +2F9C204D0A2E7A5686F7684E438DC3DCEE400AE23306D6B604A5905DB33DBF0F +50A7E7773F4BCA75247E2E9ACF9025F5DF0370A2C20E2ECE8EDC00F25761D158 +2F2B0A5F9A71FD2B1E2AA55B3C6EA75789FC5A20CE3E8790E0260E59D10E7D96 +EBBD9E861DCC0A85A2828026C98D69F882F05CE2DE0CD58AB06FE2E778CBEABC40E591AE59EEA3A4012F9266E61D4971 +8716AE40A23BB9D23DC9EE0917D7E2D50891A4D8495F08174953A883B4EA61DA +F2B9B78FA7239BB61E4FB5DDA4286C5FA2CFF34A4456781C55AC4E23644E2A46 +D570A2BD25921E6E3409E0017BC9547F1BFF0992EDC35F0BC0C526B03738D8F0 +BDA88E42BAA3025E730AF8244F2F0DC5E8F9B531882A5E1EF48BF6F0B86DB186 +03E2FFCDF9243D7DB19DAEAF2563D2CF604D82E73E501071523EA1DD3E3A1313 +702A8F9F8F5D670D999D2D4F6388F22846B23FFC4EB53A71DAC92BED76A7D1111C0536FA5293B148B501E8BE70F84C3F +A605F5B456CD733EAD4E12432617936494C1ACA42B2CEE639151AD8846D02EFBF17575565A458B7E8D6E2929BEE70ADB +F5628A7A8602546DD4BAB166DF796B7011A3F66F896599FFC7467045B1B3EB4F +BE6B94C4A7C701FBF7D189FA3C371DC7D208CF84E1EEC00A4C2F4DC14BF8CC17 +AFF0D5E080A7DAD2AFE0C498D3DFD58EAA0AE4F4F96883B46C36537B66D344C2 +E0435324027761281E17E62CCDB964E7B87EA673205DC76109570DEEA25E9C86 +84EB0F58B6F3ED2B6D62BA21D0EBCA0A1C7E17C792EBB47A2EA596AB6E4E98A78908E5E31CCE35CD8091F8D39DA52EF5 +C4027456DE3BF67B080AC563B2F38318B04DB5ED968D33D7493F0FC5CC51B165 +7C59386D2F5AE8F14AA3C1FBD953F5B7B153A3B94B20BA51C18B18BF2F0DB037 +A8E7A91824FA42085CEEE47A80A9269779AE69AFADFA210E8A4C48585F827FCB99509686D7C62215E556DBE73BB476B5 +514F6030D39CC0DE60B0280EC55F32CE93CA5D9446E7DDF03949AAB7C1276CA036A65A5B58C0904158117768D0AA22A6 +F75FC0AA714F37B75E2FDAFB837B671CD7A349A8D77565C6C0F2943A4B0B88A8 +E8C43768DA5C853253C9FD9D2112A66C509DAA37601CBD83DBFA59329E015D40 +2006DA52D75063F76A99A726E0E7B21A737A7D759C470867FF7EECC61B6A5637 +5B474E6EC7E10EB5415B5593E2C03ED5C079B2CFE2B7BCBC8B3D758727043CD4 +1F3328F14AE9C0867143929D0020AF17F58D09C1B47DBD92FFEAFD2260779DFD +CB194E675F700497AA04686CB17CF77F4295D095A0500D41FD0DC2C4FD8BE294 +328E577B4A997108194BEE91E68190663C4AA3E142B8BEF2A99B70CB4D11897DAA990D5F1FC3D33098982D73F63A26DA +4D5DC5AEF974F0E15A0B06726AC0D2589B1C931B848BFD15BFFE01B8F3BF8E50 +A9B1734BCB160609DF5B02E3C9E459D2DE3297920CBAA7677692C27886527D82 +A161A69B3BE5975F8742AE5461A4983201D7B6B91978D87C0E0D71BF74F5A6D7 +FE3F0F9875D8D2C6E51923E610A812E6BAC4E65FCD4026AA965600A4235AF3EE03D27B793C4E629948BADA5C44EE8915 From 221efbca0c8ae734fd2204897ee3af0ac9f8a182 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 15 Mar 2026 02:45:41 +0300 Subject: [PATCH 123/202] upd changelog --- CHANGELOG.md | 3 ++- documentation/SubGHzSupportedSystems.md | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cda75a330..12f30cf45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,9 @@ - Current API: 87.6 * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol +* SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**12mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**14mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 8c552e9c7..87615f2b0 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -163,6 +163,10 @@ 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) *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.* From 6899ba9351ddf2326fe929366f5acba9a250402c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 20 Mar 2026 03:09:20 +0300 Subject: [PATCH 124/202] sync apps --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f30cf45..fb5a6123c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**14mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**20mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

From b1796cc28d590deec2cd84bbbf794c574c80ab71 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:46:28 +0300 Subject: [PATCH 125/202] NFC: Fix BusFault in Write to Initial Card OFW PR 4362 by akrylysov --- .../protocols/mf_classic/mf_classic_poller.c | 37 +++++++++++-------- .../mf_classic/mf_classic_poller_i.h | 1 + 2 files changed, 22 insertions(+), 16 deletions(-) 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; From 49ac76f953867b1704c816b5ba79b659b61395b8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 22 Mar 2026 10:47:58 +0300 Subject: [PATCH 126/202] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb5a6123c..7ef40c78b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* Apps: Build tag (**20mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW PR 4362: NFC: Fix BusFault in Write to Initial Card (by @akrylysov) +* Apps: Build tag (**22mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

From a5f6285e917240101bc86444ebf36a6b9f2b7e66 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 27 Mar 2026 12:26:36 +0300 Subject: [PATCH 127/202] fix came twee repeats value --- CHANGELOG.md | 3 ++- lib/subghz/protocols/came_twee.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ef40c78b..fdcb72b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,10 @@ * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning +* SubGHz: Fix CAME TWEE repeats count for button click * NFC: Add Mifare Ultralight C Write Support (by @haw8411) * OFW PR 4362: NFC: Fix BusFault in Write to Initial Card (by @akrylysov) -* Apps: Build tag (**22mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 55947efbf..b23db0cc9 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -109,7 +109,7 @@ 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 = 3; + 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; From ecc3e78efa3b077ca4b37f67aff8ae6f8b1e9734 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 29 Mar 2026 14:31:50 +0300 Subject: [PATCH 128/202] Fix EMV crash --- CHANGELOG.md | 4 ++-- lib/nfc/helpers/iso14443_4_layer.c | 5 ++++- lib/toolbox/bit_buffer.c | 1 - 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdcb72b34..c37572301 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning -* SubGHz: Fix CAME TWEE repeats count for button click +* SubGHz: **Fix CAME TWEE repeats count for button click** +* NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) * NFC: Add Mifare Ultralight C Write Support (by @haw8411) * OFW PR 4362: NFC: Fix BusFault in Write to Initial Card (by @akrylysov) * Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) @@ -12,7 +13,6 @@

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) -- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV ---- 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/toolbox/bit_buffer.c b/lib/toolbox/bit_buffer.c index 36869bac2..e261e80d4 100644 --- a/lib/toolbox/bit_buffer.c +++ b/lib/toolbox/bit_buffer.c @@ -58,7 +58,6 @@ void bit_buffer_copy_right(BitBuffer* buf, const BitBuffer* other, size_t start_ furi_check(buf); furi_check(other); furi_check(bit_buffer_get_size_bytes(other) > start_index); - // TODO: Fix crash furi_check(buf->capacity_bytes >= bit_buffer_get_size_bytes(other) - start_index); memcpy(buf->data, other->data + start_index, bit_buffer_get_size_bytes(other) - start_index); From 4cc5b089ff2e10dc28a23cbe36cd10a4029e6f14 Mon Sep 17 00:00:00 2001 From: mxcdoam <72457810+mxcdoam@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:15:35 +0300 Subject: [PATCH 129/202] applicatin.fam update --- applications/main/nfc/application.fam | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 275da7417..9e5caf8df 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -305,6 +305,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, From f75a1c69aa1fb26e394c15460e5f951e8e8afec5 Mon Sep 17 00:00:00 2001 From: mxcdoam <72457810+mxcdoam@users.noreply.github.com> Date: Sun, 29 Mar 2026 20:22:20 +0300 Subject: [PATCH 130/202] updated parsers --- .../nfc/plugins/supported_cards/plantain.c | 839 +++++++++++++----- .../nfc/plugins/supported_cards/sevppk_tk.c | 399 +++++++++ .../main/nfc/plugins/supported_cards/sk_tk.c | 406 +++++++++ .../nfc/plugins/supported_cards/szppk_so.c | 447 ++++++++++ .../nfc/plugins/supported_cards/two_cities.c | 2 +- .../main/nfc/resources/nfc/assets/sev_id.nfc | 124 +++ .../main/nfc/resources/nfc/assets/sk_id.nfc | 85 ++ .../main/nfc/resources/nfc/assets/sz_id.nfc | 151 ++++ 8 files changed, 2211 insertions(+), 242 deletions(-) create mode 100644 applications/main/nfc/plugins/supported_cards/sevppk_tk.c create mode 100644 applications/main/nfc/plugins/supported_cards/sk_tk.c create mode 100644 applications/main/nfc/plugins/supported_cards/szppk_so.c create mode 100644 applications/main/nfc/resources/nfc/assets/sev_id.nfc create mode 100644 applications/main/nfc/resources/nfc/assets/sk_id.nfc create mode 100644 applications/main/nfc/resources/nfc/assets/sz_id.nfc diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index add7ab560..53bacfbf2 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 + #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..ba8eef6a2 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sevppk_tk.c @@ -0,0 +1,399 @@ +//Based on parsers written by Leptoptilos and Assasinfil + +#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..42d4e7e96 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/sk_tk.c @@ -0,0 +1,406 @@ +//Based on parsers written by Leptoptilos and Assasinfil + +#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..2683f0105 --- /dev/null +++ b/applications/main/nfc/plugins/supported_cards/szppk_so.c @@ -0,0 +1,447 @@ +#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 From bdff6c7bc8bbe49cf3a1d5a2eb05afb1055b5353 Mon Sep 17 00:00:00 2001 From: mxcdoam <72457810+mxcdoam@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:03:17 +0300 Subject: [PATCH 131/202] Credits --- applications/main/nfc/plugins/supported_cards/plantain.c | 2 +- applications/main/nfc/plugins/supported_cards/sevppk_tk.c | 2 +- applications/main/nfc/plugins/supported_cards/sk_tk.c | 2 +- applications/main/nfc/plugins/supported_cards/szppk_so.c | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/applications/main/nfc/plugins/supported_cards/plantain.c b/applications/main/nfc/plugins/supported_cards/plantain.c index 53bacfbf2..a9cf2c946 100644 --- a/applications/main/nfc/plugins/supported_cards/plantain.c +++ b/applications/main/nfc/plugins/supported_cards/plantain.c @@ -1,4 +1,4 @@ -//Based on parsers written by Leptoptilos and Assasinfil +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! #include "nfc_supported_card_plugin.h" #include diff --git a/applications/main/nfc/plugins/supported_cards/sevppk_tk.c b/applications/main/nfc/plugins/supported_cards/sevppk_tk.c index ba8eef6a2..e069c8c9a 100644 --- a/applications/main/nfc/plugins/supported_cards/sevppk_tk.c +++ b/applications/main/nfc/plugins/supported_cards/sevppk_tk.c @@ -1,4 +1,4 @@ -//Based on parsers written by Leptoptilos and Assasinfil +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! #include "nfc_supported_card_plugin.h" #include diff --git a/applications/main/nfc/plugins/supported_cards/sk_tk.c b/applications/main/nfc/plugins/supported_cards/sk_tk.c index 42d4e7e96..a294ed45d 100644 --- a/applications/main/nfc/plugins/supported_cards/sk_tk.c +++ b/applications/main/nfc/plugins/supported_cards/sk_tk.c @@ -1,4 +1,4 @@ -//Based on parsers written by Leptoptilos and Assasinfil +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! #include "nfc_supported_card_plugin.h" #include diff --git a/applications/main/nfc/plugins/supported_cards/szppk_so.c b/applications/main/nfc/plugins/supported_cards/szppk_so.c index 2683f0105..16bbbd807 100644 --- a/applications/main/nfc/plugins/supported_cards/szppk_so.c +++ b/applications/main/nfc/plugins/supported_cards/szppk_so.c @@ -1,3 +1,5 @@ +//Based on parsers written by Leptoptilos and Assasinfil. Also, thanks to WillyJL () for help! + #include "nfc_supported_card_plugin.h" #include #include From a044de9cd3dcf55174541a27d5330976d0ddc8ec Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Mar 2026 00:07:22 +0300 Subject: [PATCH 132/202] Address utf8 support when uploading JavaScript application OFW PR 4364 by bekindpleaserewind --- CHANGELOG.md | 1 + applications/system/js_app/packages/fz-sdk/sdk.js | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c37572301..815ada806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * OFW PR 4362: NFC: Fix BusFault in Write to Initial Card (by @akrylysov) * Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

#### Known NFC post-refactor regressions list: diff --git a/applications/system/js_app/packages/fz-sdk/sdk.js b/applications/system/js_app/packages/fz-sdk/sdk.js index 2cd1c6ab3..1e9496c30 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"); From cb3dc8952a23862b28db9304f1b785040d65d346 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Mar 2026 00:09:19 +0300 Subject: [PATCH 133/202] upd changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 815ada806..b6821e4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,9 @@ * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * SubGHz: **Fix CAME TWEE repeats count for button click** * NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) -* NFC: Add Mifare Ultralight C Write Support (by @haw8411) -* OFW PR 4362: NFC: Fix BusFault in Write to Initial Card (by @akrylysov) +* NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) +* NFC: Add **new parsers SZPPK, SKPPK and SevPPK** (PR #981 | by @mxcdoam) +* OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) From 5d3f1a6ad24f050ae92be8765b73598954aa8676 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 30 Mar 2026 00:15:05 +0300 Subject: [PATCH 134/202] add missing part --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6821e4f4..4f9db5911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * SubGHz: **Fix CAME TWEE repeats count for button click** * NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) * NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) -* NFC: Add **new parsers SZPPK, SKPPK and SevPPK** (PR #981 | by @mxcdoam) +* NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes From b9fa2943ed782e29e7c0cd748bcdf865d9d920a7 Mon Sep 17 00:00:00 2001 From: DoniyorI Date: Tue, 31 Mar 2026 22:05:59 -0400 Subject: [PATCH 135/202] nfc: add ISO15693-3 and SLIX write-back support Add Write Single Block poller functions for ISO 15693-3 and SLIX/SLIX2, enable the Write scene for both protocols, and skip blocks that already contain the correct data to minimise RF session time and avoid timeouts on large tags (e.g. 80-block ICODE SLIX2). Tested on NXP ICODE SLIX2 (UID E0 04 01 09 05 05 09 37, 80 x 4 byte blocks). --- .../protocol_support/iso15693_3/iso15693_3.c | 70 +++++++++++- .../nfc/helpers/protocol_support/slix/slix.c | 105 +++++++++++++++++- lib/nfc/protocols/iso15693_3/iso15693_3.h | 5 + lib/nfc/protocols/iso15693_3/iso15693_3_i.c | 19 ++++ lib/nfc/protocols/iso15693_3/iso15693_3_i.h | 2 + .../protocols/iso15693_3/iso15693_3_poller.h | 42 +++++++ .../iso15693_3/iso15693_3_poller_i.c | 51 +++++++++ .../iso15693_3/iso15693_3_poller_i.h | 2 - lib/nfc/protocols/slix/slix_poller.h | 34 ++++++ lib/nfc/protocols/slix/slix_poller_i.c | 28 +++++ targets/f7/api_symbols.csv | 7 +- 11 files changed, 358 insertions(+), 7 deletions(-) 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..d68b97dc0 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,75 @@ 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 +221,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/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 32094140d..895c1f2b5 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,105 @@ 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 +252,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/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..a8a8dba87 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/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/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index bb03dcbb6..bccd2c381 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.6,, +Version,+,87.7,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -2360,11 +2360,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" @@ -3397,6 +3400,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" From 17748be7f78108f0a262cdd718358dba1951d03c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:34:26 +0300 Subject: [PATCH 136/202] upd changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f9db5911..d432473eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,12 @@ * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * SubGHz: **Fix CAME TWEE repeats count for button click** +* NFC: Add **ISO15693-3 and SLIX write-back support** (PR #984 | by @DoniyorI) * NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) * NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) * NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) -* Apps: Build tag (**27mar2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**1apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state) From 9cc61f16bfc16105f8b02ab693f1c20c6a412d3b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:34:41 +0300 Subject: [PATCH 137/202] fmt --- .../main/nfc/helpers/protocol_support/iso15693_3/iso15693_3.c | 3 ++- applications/main/nfc/helpers/protocol_support/slix/slix.c | 3 +-- lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) 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 d68b97dc0..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 @@ -127,7 +127,8 @@ static NfcCommand 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)"); + furi_string_set( + instance->text_box_store, "Incompatible card\n(block count/size\nmismatch)"); view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventPollerFailure); } else { diff --git a/applications/main/nfc/helpers/protocol_support/slix/slix.c b/applications/main/nfc/helpers/protocol_support/slix/slix.c index 895c1f2b5..020323bc2 100644 --- a/applications/main/nfc/helpers/protocol_support/slix/slix.c +++ b/applications/main/nfc/helpers/protocol_support/slix/slix.c @@ -129,8 +129,7 @@ static NfcCommand nfc_scene_write_poller_callback_slix(NfcGenericEvent event, vo 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)"); + instance->text_box_store, "Incompatible card\n(block count/size\nmismatch)"); view_dispatcher_send_custom_event( instance->view_dispatcher, NfcCustomEventPollerFailure); } else { 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 a8a8dba87..f738f2087 100644 --- a/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c +++ b/lib/nfc/protocols/iso15693_3/iso15693_3_poller_i.c @@ -277,8 +277,8 @@ Iso15693_3Error iso15693_3_poller_write_blocks( 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); + ret = + iso15693_3_poller_write_block(instance, &data[block_size * i], (uint8_t)i, block_size); if(ret != Iso15693_3ErrorNone) break; } From e17ea14ac874b45d1bd07b65d313a6c01a9783f8 Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:30:37 -0400 Subject: [PATCH 138/202] Create allstar_firefly.h --- lib/subghz/protocols/allstar_firefly.h | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 lib/subghz/protocols/allstar_firefly.h diff --git a/lib/subghz/protocols/allstar_firefly.h b/lib/subghz/protocols/allstar_firefly.h new file mode 100644 index 000000000..6ebf0dda6 --- /dev/null +++ b/lib/subghz/protocols/allstar_firefly.h @@ -0,0 +1,61 @@ +#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 + +/* Protocol name (must match what is written to .sub files) */ +#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly" + +/* Timing constants (from 37-frame analysis of two captures) */ +#define AF_FREQ 318000000UL +#define AF_BIT_COUNT 9u +#define AF_SYM_COUNT 18u + +#define AF_LONG_PULSE_US 4045u +#define AF_SHORT_PULSE_US 530u +#define AF_SHORT_GAP_US 607u +#define AF_LONG_GAP_US 4139u +#define AF_INTERFRAME_US 30440u + +#define AF_SHORT_PULSE_MIN 300u +#define AF_SHORT_PULSE_MAX 1200u +#define AF_LONG_PULSE_MIN 2500u +#define AF_LONG_PULSE_MAX 5500u +#define AF_FRAME_THRESH_US 20000u +#define AF_TX_REPEAT 20u +#define AF_TX_BUF_SIZE (AF_TX_REPEAT * AF_SYM_COUNT * 2u + 8u) + +/* Protocol vtable entries */ +extern const SubGhzProtocolDecoder subghz_protocol_decoder_allstar_firefly; +extern const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly; +extern const SubGhzProtocol subghz_protocol_allstar_firefly; From 1f2022b87c0416e5e67709f9f62de34511997790 Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:35:55 -0400 Subject: [PATCH 139/202] subghz: add Allstar Firefly 318ALD31K protocol implementation --- lib/subghz/protocols/allstar_firefly.c | 327 +++++++++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 lib/subghz/protocols/allstar_firefly.c diff --git a/lib/subghz/protocols/allstar_firefly.c b/lib/subghz/protocols/allstar_firefly.c new file mode 100644 index 000000000..cbec4a36e --- /dev/null +++ b/lib/subghz/protocols/allstar_firefly.c @@ -0,0 +1,327 @@ +/** + * allstar_firefly.c -- Allstar Firefly 318ALD31K native SubGHz protocol + * + * Implements the SubGhzProtocol vtable for the Supertex ED-9 based gate remote. + * Both the decoder state machine and encoder TX buffer are ported directly from + * the proven FAP implementation; only the framing (alloc/free/serialize/etc.) + * changes to match the native SubGHz plugin interface. + * + * See allstar_firefly.h for full protocol documentation. + */ + +#include "allstar_firefly.h" + +#include + +#include +#include +#include +#include + +#define TAG "AllstarFirefly" + +/* --- Decoder instance ------------------------------------------------------ */ + +typedef enum { + AfRxState_WaitGap, + AfRxState_Receiving, +} AfRxState; + +typedef struct { + SubGhzProtocolDecoderBase base; + AfRxState rx_state; + uint8_t rx_syms[AF_SYM_COUNT]; + uint8_t rx_count; + char dip[AF_BIT_COUNT + 1]; +} SubGhzProtocolDecoderAllstarFirefly; + +typedef struct { + SubGhzProtocolEncoderBase base; + char dip[AF_BIT_COUNT + 1]; + uint32_t tx_buf[AF_TX_BUF_SIZE]; + uint32_t tx_size; + uint32_t tx_pos; +} SubGhzProtocolEncoderAllstarFirefly; + +static bool af_decode_symbols(const uint8_t* syms, char* dip) { + for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { + uint8_t a = syms[i * 2]; + uint8_t b = syms[i * 2 + 1]; + if (a == 1 && b == 1) dip[i] = '+'; + else if (a == 0 && b == 0) dip[i] = '-'; + else if (a == 1 && b == 0) dip[i] = '0'; + else return false; + } + dip[AF_BIT_COUNT] = '\0'; + return true; +} + +static bool af_dip_valid(const char* dip) { + if(!dip) return false; + for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { + if(dip[i] != '+' && dip[i] != '-' && dip[i] != '0') return false; + } + return (dip[AF_BIT_COUNT] == '\0'); +} + +static uint32_t af_dip_to_uint32(const char* dip) { + uint32_t val = 0; + for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { + val *= 3; + if (dip[i] == '+') val += 2; + else if(dip[i] == '0') val += 1; + } + return val; +} + +static uint32_t af_build_tx_buf(const char* dip, uint32_t* buf) { + uint32_t pos = 0; + for(uint32_t rep = 0; rep < AF_TX_REPEAT; rep++) { + for(uint32_t bit = 0; bit < AF_BIT_COUNT; bit++) { + bool last = (bit == AF_BIT_COUNT - 1); + char c = dip[bit]; + uint32_t p0, g0, p1, g1; + if(c == '+') { + p0 = AF_LONG_PULSE_US; g0 = AF_SHORT_GAP_US; + p1 = AF_LONG_PULSE_US; g1 = AF_SHORT_GAP_US; + } else if(c == '-') { + p0 = AF_SHORT_PULSE_US; g0 = AF_LONG_GAP_US; + p1 = AF_SHORT_PULSE_US; g1 = AF_LONG_GAP_US; + } else { + p0 = AF_LONG_PULSE_US; g0 = AF_SHORT_GAP_US; + p1 = AF_SHORT_PULSE_US; g1 = AF_LONG_GAP_US; + } + if(last) g1 = AF_INTERFRAME_US; + buf[pos++] = p0; buf[pos++] = g0; + buf[pos++] = p1; buf[pos++] = g1; + } + } + return pos; +} +static void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderAllstarFirefly* instance = + malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly)); + instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; + memset(instance->dip, '-', AF_BIT_COUNT); + instance->dip[AF_BIT_COUNT] = '\0'; + return instance; +} + +static void subghz_protocol_decoder_allstar_firefly_free(void* context) { + furi_assert(context); free(context); +} + +static void subghz_protocol_decoder_allstar_firefly_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; +} + +static void subghz_protocol_decoder_allstar_firefly_feed( + void* context, bool level, uint32_t duration) { + + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + + if(level) { + if(instance->rx_state == AfRxState_Receiving) { + uint8_t sym; + if(duration >= AF_LONG_PULSE_MIN && duration <= AF_LONG_PULSE_MAX) { + sym = 1u; + } else if(duration >= AF_SHORT_PULSE_MIN && duration <= AF_SHORT_PULSE_MAX) { + sym = 0u; + } else { + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; + return; + } + if(instance->rx_count < AF_SYM_COUNT) { + instance->rx_syms[instance->rx_count++] = sym; + } + } + } else { + if(duration >= AF_FRAME_THRESH_US) { + if(instance->rx_state == AfRxState_Receiving && + instance->rx_count == AF_SYM_COUNT) { + char decoded[AF_BIT_COUNT + 1]; + if(af_decode_symbols(instance->rx_syms, decoded)) { + memcpy(instance->dip, decoded, AF_BIT_COUNT + 1); + if(instance->base.callback) { + instance->base.callback( + &instance->base, + instance->base.context); + } + } + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; + } else if(instance->rx_state == AfRxState_WaitGap) { + instance->rx_state = AfRxState_Receiving; + instance->rx_count = 0; + } else { + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; + } + } + } +} + +static uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + uint8_t hash = 0; + for(uint8_t i = 0; i < AF_BIT_COUNT; i++) hash ^= (uint8_t)instance->dip[i]; + return hash; +} +static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize( + void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { + + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Failed to write Frequency"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_string(flipper_format, "Preset", preset->name)) { + FURI_LOG_E(TAG, "Failed to write Preset"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_string_cstr( + flipper_format, "Protocol", SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME)) { + FURI_LOG_E(TAG, "Failed to write Protocol"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_string_cstr(flipper_format, "Key", instance->dip)) { + FURI_LOG_E(TAG, "Failed to write Key"); + return SubGhzProtocolStatusErrorParserOthers; + } + return SubGhzProtocolStatusOk; +} + +static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( + void* context, FlipperFormat* flipper_format) { + + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + FuriString* key_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Key", key_str)) { + FURI_LOG_E(TAG, "Missing Key field"); + furi_string_free(key_str); + return SubGhzProtocolStatusErrorParserOthers; + } + const char* key_cstr = furi_string_get_cstr(key_str); + if(strlen(key_cstr) != AF_BIT_COUNT || !af_dip_valid(key_cstr)) { + FURI_LOG_E(TAG, "Invalid Key value: %s", key_cstr); + furi_string_free(key_str); + return SubGhzProtocolStatusErrorParserOthers; + } + memcpy(instance->dip, key_cstr, AF_BIT_COUNT + 1); + furi_string_free(key_str); + return SubGhzProtocolStatusOk; +} + +static void subghz_protocol_decoder_allstar_firefly_get_string( + void* context, FuriString* output) { + + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + uint32_t code = af_dip_to_uint32(instance->dip); + furi_string_cat_printf( + output, + "%s\r\n0x%04lX\r\n" + "Freq: 318MHz OOK\r\n" + "1 2 3 4 5 6 7 8 9\r\n" + "%c %c %c %c %c %c %c %c %c", + SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME, + (unsigned long)code, + instance->dip[0], instance->dip[1], instance->dip[2], + instance->dip[3], instance->dip[4], instance->dip[5], + instance->dip[6], instance->dip[7], instance->dip[8]); +} +static void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderAllstarFirefly* instance = + malloc(sizeof(SubGhzProtocolEncoderAllstarFirefly)); + instance->base.protocol = &subghz_protocol_allstar_firefly; + memset(instance->dip, '-', AF_BIT_COUNT); + instance->dip[AF_BIT_COUNT] = '\0'; + instance->tx_size = 0; + instance->tx_pos = 0; + return instance; +} + +static void subghz_protocol_encoder_allstar_firefly_free(void* context) { + furi_assert(context); free(context); +} + +static void subghz_protocol_encoder_allstar_firefly_stop(void* context) { + UNUSED(context); +} + +static SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( + void* context, FlipperFormat* flipper_format) { + + furi_assert(context); + SubGhzProtocolEncoderAllstarFirefly* instance = context; + FuriString* key_str = furi_string_alloc(); + if(!flipper_format_read_string(flipper_format, "Key", key_str)) { + FURI_LOG_E(TAG, "Encoder: missing Key field"); + furi_string_free(key_str); + return SubGhzProtocolStatusErrorParserOthers; + } + const char* key_cstr = furi_string_get_cstr(key_str); + if(strlen(key_cstr) != AF_BIT_COUNT || !af_dip_valid(key_cstr)) { + FURI_LOG_E(TAG, "Encoder: invalid Key: %s", key_cstr); + furi_string_free(key_str); + return SubGhzProtocolStatusErrorParserOthers; + } + memcpy(instance->dip, key_cstr, AF_BIT_COUNT + 1); + furi_string_free(key_str); + instance->tx_size = af_build_tx_buf(instance->dip, instance->tx_buf); + instance->tx_pos = 0; + return SubGhzProtocolStatusOk; +} + +static LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) { + furi_assert(context); + SubGhzProtocolEncoderAllstarFirefly* instance = context; + if(instance->tx_pos >= instance->tx_size) { + return level_duration_reset(); + } + bool lv = (instance->tx_pos % 2 == 0); + uint32_t dur = instance->tx_buf[instance->tx_pos++]; + return level_duration_make(lv, dur); +} +const SubGhzProtocolDecoder subghz_protocol_decoder_allstar_firefly = { + .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 = 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, +}; + +const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly = { + .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_AM | + SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + .decoder = &subghz_protocol_decoder_allstar_firefly, + .encoder = &subghz_protocol_encoder_allstar_firefly, +}; From e7098821ca062d462e186dcc7aace32231c3c0f5 Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:39:04 -0400 Subject: [PATCH 140/202] Update protocol_items.h --- lib/subghz/protocols/protocol_items.h | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index d9e5f0cd0..57930bdf5 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -2,6 +2,7 @@ #include "../registry.h" #include "../subghz_protocol_registry.h" +#include "allstar_firefly.h" #include "princeton.h" #include "keeloq.h" #include "nice_flo.h" From fa1d01d70f1c1bb40d15d54c3e6420e7fa920e62 Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:40:49 -0400 Subject: [PATCH 141/202] subghz: register Allstar Firefly in protocol_items.c --- lib/subghz/protocols/protocol_items.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 416e3524f..25a0420ee 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -29,7 +29,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &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_nord_ice, &subghz_protocol_allstar_firefly, }; const SubGhzProtocolRegistry subghz_protocol_registry = { From fd4e552c703891899587458797c9816b31b18614 Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 01:41:44 -0400 Subject: [PATCH 142/202] subghz: register Allstar Firefly in protocol_items.c --- lib/subghz/protocols/protocol_items.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 57930bdf5..3a2b763f1 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -1,8 +1,6 @@ #pragma once #include "../registry.h" #include "../subghz_protocol_registry.h" - -#include "allstar_firefly.h" #include "princeton.h" #include "keeloq.h" #include "nice_flo.h" @@ -60,3 +58,4 @@ #include "ditec_gol4.h" #include "keyfinder.h" #include "nord_ice.h" +#include "allstar_firefly.h" From a663836b7065ac181fcd733bd0d9dd9f9b60a032 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 19 Apr 2026 20:26:07 +0200 Subject: [PATCH 143/202] NFC: Fix CLI with NTAG4xx and Type 4 Tag support --nobuild --- CHANGELOG.md | 1 + applications/main/nfc/application.fam | 4 ++- .../cli/commands/dump/nfc_cli_command_dump.c | 24 ++++++++------ .../protocols/ntag4xx/nfc_cli_dump_ntag4xx.c | 31 +++++++++++++++++++ .../protocols/ntag4xx/nfc_cli_dump_ntag4xx.h | 5 +++ .../type_4_tag/nfc_cli_dump_type_4_tag.c | 31 +++++++++++++++++++ .../type_4_tag/nfc_cli_dump_type_4_tag.h | 5 +++ .../nfc/cli/commands/helpers/nfc_cli_format.c | 4 ++- .../cli/commands/raw/nfc_cli_command_raw.c | 3 ++ 9 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.h diff --git a/CHANGELOG.md b/CHANGELOG.md index bab67b77d..f9f0d3519 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ - 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: Settings: Storage settings exit scenes properly if used via favourites (by @xMasterX) - UL: UI: Some small changes (by @xMasterX) diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 89dbcf42c..903e5fe2a 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -605,13 +605,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) { From f4b30cdbc7cc112a7e8d7ca129fa0fd90a0eee8e Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 19 Apr 2026 23:45:17 +0200 Subject: [PATCH 144/202] Sub-GHz: Fix display of memory full --- CHANGELOG.md | 1 + applications/main/subghz/scenes/subghz_scene_receiver.c | 4 ++-- applications/main/subghz/subghz_history.c | 4 ++-- applications/main/subghz/views/receiver.c | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bab67b77d..44b2643f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ ### 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 and cleanup (by @xMasterX) diff --git a/applications/main/subghz/scenes/subghz_scene_receiver.c b/applications/main/subghz/scenes/subghz_scene_receiver.c index 10a6c141c..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, diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 697c9ff5b..0bbd72391 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -199,11 +199,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; } } 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; From 0ac7533a2366fc6bcf372cda8a54f6bf3f42762b Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 19 Apr 2026 23:45:31 +0200 Subject: [PATCH 145/202] Sub-GHz: Update space taken by each RPC session --- applications/main/subghz/subghz_history.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 0bbd72391..81ca18191 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -5,7 +5,7 @@ #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 TAG "SubGhzHistory" From f312aa79bc75ad6cc4f3067cf8e83e2fb29b360b Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 19 Apr 2026 23:53:23 +0200 Subject: [PATCH 146/202] Sub-GHz: Temporarily lower reserved memory --- CHANGELOG.md | 6 ++++++ applications/main/subghz/subghz_history.c | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44b2643f3..ca40c1f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### Known Issues: +- Sub-GHz app is now external due to running out of space in flash, but it uses almost all available RAM. This means that: + - qFlipper / Mobile App cannot be used together with Sub-GHz app for now + - Only 20-30 signals can be kept at a time in receive history while in Read mode + - Opening Sub-GHz after using a few other apps may result in an out of memory warning, simply reboot and it will work again + ### Added: - Apps: - Games: Checkers (by @H4W9) diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 81ca18191..be61d3526 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 (23624 * (1 - MIN(rpc_get_sessions_count(instance->rpc), 1U))) +// #define SUBGHZ_HISTORY_FREE_HEAP (23624 * (1 - MIN(rpc_get_sessions_count(instance->rpc), 1U))) +#define SUBGHZ_HISTORY_FREE_HEAP (2048) #define TAG "SubGhzHistory" From b5fff36498205fc4a82e4dd08c6f6bbec953220e Mon Sep 17 00:00:00 2001 From: jlaughter <53415135+jlaughter@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:19:02 -0400 Subject: [PATCH 147/202] subghz: fix Allstar Firefly serialize/deserialize to use standard block_generic format --- lib/subghz/protocols/allstar_firefly.c | 163 ++++++++++++------------- 1 file changed, 75 insertions(+), 88 deletions(-) diff --git a/lib/subghz/protocols/allstar_firefly.c b/lib/subghz/protocols/allstar_firefly.c index cbec4a36e..29fa86d15 100644 --- a/lib/subghz/protocols/allstar_firefly.c +++ b/lib/subghz/protocols/allstar_firefly.c @@ -2,9 +2,18 @@ * allstar_firefly.c -- Allstar Firefly 318ALD31K native SubGHz protocol * * Implements the SubGhzProtocol vtable for the Supertex ED-9 based gate remote. - * Both the decoder state machine and encoder TX buffer are ported directly from - * the proven FAP implementation; only the framing (alloc/free/serialize/etc.) - * changes to match the native SubGHz plugin interface. + * 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' = 1, '-' = 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. */ @@ -12,6 +21,7 @@ #include "allstar_firefly.h" #include +#include #include #include @@ -20,8 +30,6 @@ #define TAG "AllstarFirefly" -/* --- Decoder instance ------------------------------------------------------ */ - typedef enum { AfRxState_WaitGap, AfRxState_Receiving, @@ -29,6 +37,7 @@ typedef enum { typedef struct { SubGhzProtocolDecoderBase base; + SubGhzBlockGeneric generic; AfRxState rx_state; uint8_t rx_syms[AF_SYM_COUNT]; uint8_t rx_count; @@ -37,6 +46,7 @@ typedef struct { typedef struct { SubGhzProtocolEncoderBase base; + SubGhzBlockGeneric generic; char dip[AF_BIT_COUNT + 1]; uint32_t tx_buf[AF_TX_BUF_SIZE]; uint32_t tx_size; @@ -64,8 +74,8 @@ static bool af_dip_valid(const char* dip) { return (dip[AF_BIT_COUNT] == '\0'); } -static uint32_t af_dip_to_uint32(const char* dip) { - uint32_t val = 0; +static uint64_t af_dip_to_uint64(const char* dip) { + uint64_t val = 0; for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { val *= 3; if (dip[i] == '+') val += 2; @@ -74,6 +84,17 @@ static uint32_t af_dip_to_uint32(const char* dip) { return val; } +static void af_uint64_to_dip(uint64_t val, char* dip) { + for(int8_t i = AF_BIT_COUNT - 1; i >= 0; i--) { + uint8_t rem = (uint8_t)(val % 3); + val /= 3; + if (rem == 2) dip[i] = '+'; + else if(rem == 1) dip[i] = '0'; + else dip[i] = '-'; + } + dip[AF_BIT_COUNT] = '\0'; +} + static uint32_t af_build_tx_buf(const char* dip, uint32_t* buf) { uint32_t pos = 0; for(uint32_t rep = 0; rep < AF_TX_REPEAT; rep++) { @@ -98,15 +119,19 @@ static uint32_t af_build_tx_buf(const char* dip, uint32_t* buf) { } return pos; } + static void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolDecoderAllstarFirefly* instance = malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly)); - instance->base.protocol = &subghz_protocol_allstar_firefly; - instance->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; + instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->generic.protocol_name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME; + instance->generic.data = 0; + instance->generic.data_count_bit = AF_SYM_COUNT; + instance->rx_state = AfRxState_WaitGap; + instance->rx_count = 0; memset(instance->dip, '-', AF_BIT_COUNT); - instance->dip[AF_BIT_COUNT] = '\0'; + instance->dip[AF_BIT_COUNT] = '\0'; return instance; } @@ -139,9 +164,8 @@ static void subghz_protocol_decoder_allstar_firefly_feed( instance->rx_count = 0; return; } - if(instance->rx_count < AF_SYM_COUNT) { + if(instance->rx_count < AF_SYM_COUNT) instance->rx_syms[instance->rx_count++] = sym; - } } } else { if(duration >= AF_FRAME_THRESH_US) { @@ -150,11 +174,10 @@ static void subghz_protocol_decoder_allstar_firefly_feed( char decoded[AF_BIT_COUNT + 1]; if(af_decode_symbols(instance->rx_syms, decoded)) { memcpy(instance->dip, decoded, AF_BIT_COUNT + 1); - if(instance->base.callback) { - instance->base.callback( - &instance->base, - instance->base.context); - } + instance->generic.data = af_dip_to_uint64(decoded); + instance->generic.data_count_bit = AF_SYM_COUNT; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); } instance->rx_state = AfRxState_WaitGap; instance->rx_count = 0; @@ -176,59 +199,30 @@ static uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* conte for(uint8_t i = 0; i < AF_BIT_COUNT; i++) hash ^= (uint8_t)instance->dip[i]; return hash; } + static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { - furi_assert(context); SubGhzProtocolDecoderAllstarFirefly* instance = context; - if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { - FURI_LOG_E(TAG, "Failed to write Frequency"); - return SubGhzProtocolStatusErrorParserOthers; - } - if(!flipper_format_write_string(flipper_format, "Preset", preset->name)) { - FURI_LOG_E(TAG, "Failed to write Preset"); - return SubGhzProtocolStatusErrorParserOthers; - } - if(!flipper_format_write_string_cstr( - flipper_format, "Protocol", SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME)) { - FURI_LOG_E(TAG, "Failed to write Protocol"); - return SubGhzProtocolStatusErrorParserOthers; - } - if(!flipper_format_write_string_cstr(flipper_format, "Key", instance->dip)) { - FURI_LOG_E(TAG, "Failed to write Key"); - return SubGhzProtocolStatusErrorParserOthers; - } - return SubGhzProtocolStatusOk; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); } static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( void* context, FlipperFormat* flipper_format) { - furi_assert(context); SubGhzProtocolDecoderAllstarFirefly* instance = context; - FuriString* key_str = furi_string_alloc(); - if(!flipper_format_read_string(flipper_format, "Key", key_str)) { - FURI_LOG_E(TAG, "Missing Key field"); - furi_string_free(key_str); - return SubGhzProtocolStatusErrorParserOthers; - } - const char* key_cstr = furi_string_get_cstr(key_str); - if(strlen(key_cstr) != AF_BIT_COUNT || !af_dip_valid(key_cstr)) { - FURI_LOG_E(TAG, "Invalid Key value: %s", key_cstr); - furi_string_free(key_str); - return SubGhzProtocolStatusErrorParserOthers; - } - memcpy(instance->dip, key_cstr, AF_BIT_COUNT + 1); - furi_string_free(key_str); + SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, AF_SYM_COUNT); + if(status != SubGhzProtocolStatusOk) return status; + af_uint64_to_dip(instance->generic.data, instance->dip); + if(!af_dip_valid(instance->dip)) return SubGhzProtocolStatusErrorParserOthers; return SubGhzProtocolStatusOk; } static void subghz_protocol_decoder_allstar_firefly_get_string( void* context, FuriString* output) { - furi_assert(context); SubGhzProtocolDecoderAllstarFirefly* instance = context; - uint32_t code = af_dip_to_uint32(instance->dip); furi_string_cat_printf( output, "%s\r\n0x%04lX\r\n" @@ -236,20 +230,24 @@ static void subghz_protocol_decoder_allstar_firefly_get_string( "1 2 3 4 5 6 7 8 9\r\n" "%c %c %c %c %c %c %c %c %c", SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME, - (unsigned long)code, + (unsigned long)(instance->generic.data), instance->dip[0], instance->dip[1], instance->dip[2], instance->dip[3], instance->dip[4], instance->dip[5], instance->dip[6], instance->dip[7], instance->dip[8]); } + static void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { UNUSED(environment); SubGhzProtocolEncoderAllstarFirefly* instance = malloc(sizeof(SubGhzProtocolEncoderAllstarFirefly)); - instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->base.protocol = &subghz_protocol_allstar_firefly; + instance->generic.protocol_name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME; + instance->generic.data = 0; + instance->generic.data_count_bit = AF_SYM_COUNT; memset(instance->dip, '-', AF_BIT_COUNT); - instance->dip[AF_BIT_COUNT] = '\0'; - instance->tx_size = 0; - instance->tx_pos = 0; + instance->dip[AF_BIT_COUNT] = '\0'; + instance->tx_size = 0; + instance->tx_pos = 0; return instance; } @@ -263,23 +261,13 @@ static void subghz_protocol_encoder_allstar_firefly_stop(void* context) { static SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( void* context, FlipperFormat* flipper_format) { - furi_assert(context); SubGhzProtocolEncoderAllstarFirefly* instance = context; - FuriString* key_str = furi_string_alloc(); - if(!flipper_format_read_string(flipper_format, "Key", key_str)) { - FURI_LOG_E(TAG, "Encoder: missing Key field"); - furi_string_free(key_str); - return SubGhzProtocolStatusErrorParserOthers; - } - const char* key_cstr = furi_string_get_cstr(key_str); - if(strlen(key_cstr) != AF_BIT_COUNT || !af_dip_valid(key_cstr)) { - FURI_LOG_E(TAG, "Encoder: invalid Key: %s", key_cstr); - furi_string_free(key_str); - return SubGhzProtocolStatusErrorParserOthers; - } - memcpy(instance->dip, key_cstr, AF_BIT_COUNT + 1); - furi_string_free(key_str); + SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, AF_SYM_COUNT); + if(status != SubGhzProtocolStatusOk) return status; + af_uint64_to_dip(instance->generic.data, instance->dip); + if(!af_dip_valid(instance->dip)) return SubGhzProtocolStatusErrorParserOthers; instance->tx_size = af_build_tx_buf(instance->dip, instance->tx_buf); instance->tx_pos = 0; return SubGhzProtocolStatusOk; @@ -288,22 +276,21 @@ static SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( static LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) { furi_assert(context); SubGhzProtocolEncoderAllstarFirefly* instance = context; - if(instance->tx_pos >= instance->tx_size) { - return level_duration_reset(); - } + if(instance->tx_pos >= instance->tx_size) return level_duration_reset(); bool lv = (instance->tx_pos % 2 == 0); uint32_t dur = instance->tx_buf[instance->tx_pos++]; return level_duration_make(lv, dur); } + const SubGhzProtocolDecoder subghz_protocol_decoder_allstar_firefly = { - .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 = 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, + .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 = 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, }; const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly = { @@ -317,10 +304,10 @@ const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly = { const SubGhzProtocol subghz_protocol_allstar_firefly = { .name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME, .type = SubGhzProtocolTypeStatic, - .flag = SubGhzProtocolFlag_AM | + .flag = SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_decoder_allstar_firefly, .encoder = &subghz_protocol_encoder_allstar_firefly, From d5fad4f38b1ef0f5774e6ef0b95dcf71392a91dc Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Apr 2026 01:56:55 +0300 Subject: [PATCH 148/202] Remake to use other protocols codebase Also remake DIP UI and funcs --- lib/subghz/protocols/allstar_firefly.c | 554 ++++++++++++++----------- lib/subghz/protocols/allstar_firefly.h | 126 +++++- 2 files changed, 412 insertions(+), 268 deletions(-) diff --git a/lib/subghz/protocols/allstar_firefly.c b/lib/subghz/protocols/allstar_firefly.c index 29fa86d15..8f85a397f 100644 --- a/lib/subghz/protocols/allstar_firefly.c +++ b/lib/subghz/protocols/allstar_firefly.c @@ -4,7 +4,7 @@ * 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' = 1, '-' = 0). + * first: '+' = 2, '0' = 3, '-' = 0). * * Saved file format: * Filetype: Flipper SubGhz Key File @@ -20,295 +20,357 @@ #include "allstar_firefly.h" -#include -#include - -#include -#include -#include -#include +#include "../blocks/const.h" +#include "../blocks/decoder.h" +#include "../blocks/encoder.h" +#include "../blocks/generic.h" +#include "../blocks/math.h" #define TAG "AllstarFirefly" -typedef enum { - AfRxState_WaitGap, - AfRxState_Receiving, -} AfRxState; +#define DIP_P 0b11 //(+) +#define DIP_O 0b10 //(0) +#define DIP_N 0b00 //(-) -typedef struct { +#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; - SubGhzBlockGeneric generic; - AfRxState rx_state; - uint8_t rx_syms[AF_SYM_COUNT]; - uint8_t rx_count; - char dip[AF_BIT_COUNT + 1]; -} SubGhzProtocolDecoderAllstarFirefly; -typedef struct { + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderAllstarFirefly { SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; SubGhzBlockGeneric generic; - char dip[AF_BIT_COUNT + 1]; - uint32_t tx_buf[AF_TX_BUF_SIZE]; - uint32_t tx_size; - uint32_t tx_pos; -} SubGhzProtocolEncoderAllstarFirefly; +}; -static bool af_decode_symbols(const uint8_t* syms, char* dip) { - for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { - uint8_t a = syms[i * 2]; - uint8_t b = syms[i * 2 + 1]; - if (a == 1 && b == 1) dip[i] = '+'; - else if (a == 0 && b == 0) dip[i] = '-'; - else if (a == 1 && b == 0) dip[i] = '0'; - else return false; - } - dip[AF_BIT_COUNT] = '\0'; - return true; -} +typedef enum { + AllstarFireflyDecoderStepReset = 0, + AllstarFireflyDecoderStepSaveDuration, + AllstarFireflyDecoderStepCheckDuration, +} AllstarFireflyDecoderStep; -static bool af_dip_valid(const char* dip) { - if(!dip) return false; - for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { - if(dip[i] != '+' && dip[i] != '-' && dip[i] != '0') return false; - } - return (dip[AF_BIT_COUNT] == '\0'); -} +const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder = { + .alloc = subghz_protocol_decoder_allstar_firefly_alloc, + .free = subghz_protocol_decoder_allstar_firefly_free, -static uint64_t af_dip_to_uint64(const char* dip) { - uint64_t val = 0; - for(uint8_t i = 0; i < AF_BIT_COUNT; i++) { - val *= 3; - if (dip[i] == '+') val += 2; - else if(dip[i] == '0') val += 1; - } - return val; -} + .feed = subghz_protocol_decoder_allstar_firefly_feed, + .reset = subghz_protocol_decoder_allstar_firefly_reset, -static void af_uint64_to_dip(uint64_t val, char* dip) { - for(int8_t i = AF_BIT_COUNT - 1; i >= 0; i--) { - uint8_t rem = (uint8_t)(val % 3); - val /= 3; - if (rem == 2) dip[i] = '+'; - else if(rem == 1) dip[i] = '0'; - else dip[i] = '-'; - } - dip[AF_BIT_COUNT] = '\0'; -} + .get_hash_data = 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, +}; -static uint32_t af_build_tx_buf(const char* dip, uint32_t* buf) { - uint32_t pos = 0; - for(uint32_t rep = 0; rep < AF_TX_REPEAT; rep++) { - for(uint32_t bit = 0; bit < AF_BIT_COUNT; bit++) { - bool last = (bit == AF_BIT_COUNT - 1); - char c = dip[bit]; - uint32_t p0, g0, p1, g1; - if(c == '+') { - p0 = AF_LONG_PULSE_US; g0 = AF_SHORT_GAP_US; - p1 = AF_LONG_PULSE_US; g1 = AF_SHORT_GAP_US; - } else if(c == '-') { - p0 = AF_SHORT_PULSE_US; g0 = AF_LONG_GAP_US; - p1 = AF_SHORT_PULSE_US; g1 = AF_LONG_GAP_US; - } else { - p0 = AF_LONG_PULSE_US; g0 = AF_SHORT_GAP_US; - p1 = AF_SHORT_PULSE_US; g1 = AF_LONG_GAP_US; - } - if(last) g1 = AF_INTERFRAME_US; - buf[pos++] = p0; buf[pos++] = g0; - buf[pos++] = p1; buf[pos++] = g1; - } - } - return pos; -} +const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder = { + .alloc = subghz_protocol_encoder_allstar_firefly_alloc, + .free = subghz_protocol_encoder_allstar_firefly_free, -static void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) { + .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); - SubGhzProtocolDecoderAllstarFirefly* instance = - malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly)); - instance->base.protocol = &subghz_protocol_allstar_firefly; - instance->generic.protocol_name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME; - instance->generic.data = 0; - instance->generic.data_count_bit = AF_SYM_COUNT; - instance->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; - memset(instance->dip, '-', AF_BIT_COUNT); - instance->dip[AF_BIT_COUNT] = '\0'; + 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; } -static void subghz_protocol_decoder_allstar_firefly_free(void* context) { - furi_assert(context); free(context); +void subghz_protocol_encoder_allstar_firefly_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderAllstarFirefly* instance = context; + free(instance->encoder.upload); + free(instance); } -static void subghz_protocol_decoder_allstar_firefly_reset(void* context) { - furi_assert(context); - SubGhzProtocolDecoderAllstarFirefly* instance = context; - instance->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; -} +/** + * 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; -static void subghz_protocol_decoder_allstar_firefly_feed( - void* context, bool level, uint32_t duration) { - - furi_assert(context); - SubGhzProtocolDecoderAllstarFirefly* instance = context; - - if(level) { - if(instance->rx_state == AfRxState_Receiving) { - uint8_t sym; - if(duration >= AF_LONG_PULSE_MIN && duration <= AF_LONG_PULSE_MAX) { - sym = 1u; - } else if(duration >= AF_SHORT_PULSE_MIN && duration <= AF_SHORT_PULSE_MAX) { - sym = 0u; + // 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->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; - return; + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_allstar_firefly_const.te_short); } - if(instance->rx_count < AF_SYM_COUNT) - instance->rx_syms[instance->rx_count++] = sym; - } - } else { - if(duration >= AF_FRAME_THRESH_US) { - if(instance->rx_state == AfRxState_Receiving && - instance->rx_count == AF_SYM_COUNT) { - char decoded[AF_BIT_COUNT + 1]; - if(af_decode_symbols(instance->rx_syms, decoded)) { - memcpy(instance->dip, decoded, AF_BIT_COUNT + 1); - instance->generic.data = af_dip_to_uint64(decoded); - instance->generic.data_count_bit = AF_SYM_COUNT; - if(instance->base.callback) - instance->base.callback(&instance->base, instance->base.context); - } - instance->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; - } else if(instance->rx_state == AfRxState_WaitGap) { - instance->rx_state = AfRxState_Receiving; - instance->rx_count = 0; + } 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->rx_state = AfRxState_WaitGap; - instance->rx_count = 0; + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_allstar_firefly_const.te_long); } } } + + instance->encoder.size_upload = index; + return; } -static uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) { +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; - uint8_t hash = 0; - for(uint8_t i = 0; i < AF_BIT_COUNT; i++) hash ^= (uint8_t)instance->dip[i]; - return hash; + free(instance); } -static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize( - void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { +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; + } +} + +uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderAllstarFirefly* instance = context; + return subghz_protocol_blocks_get_hash_data( + &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); } -static SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( - void* context, FlipperFormat* flipper_format) { +SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize( + void* context, + FlipperFormat* flipper_format) { furi_assert(context); SubGhzProtocolDecoderAllstarFirefly* instance = context; - SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, AF_SYM_COUNT); - if(status != SubGhzProtocolStatusOk) return status; - af_uint64_to_dip(instance->generic.data, instance->dip); - if(!af_dip_valid(instance->dip)) return SubGhzProtocolStatusErrorParserOthers; - return SubGhzProtocolStatusOk; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_allstar_firefly_const.min_count_bit_for_found); } -static void subghz_protocol_decoder_allstar_firefly_get_string( - void* context, FuriString* output) { +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\r\n0x%04lX\r\n" - "Freq: 318MHz OOK\r\n" - "1 2 3 4 5 6 7 8 9\r\n" - "%c %c %c %c %c %c %c %c %c", - SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME, - (unsigned long)(instance->generic.data), - instance->dip[0], instance->dip[1], instance->dip[2], - instance->dip[3], instance->dip[4], instance->dip[5], - instance->dip[6], instance->dip[7], instance->dip[8]); + "%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)); } - -static 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 = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME; - instance->generic.data = 0; - instance->generic.data_count_bit = AF_SYM_COUNT; - memset(instance->dip, '-', AF_BIT_COUNT); - instance->dip[AF_BIT_COUNT] = '\0'; - instance->tx_size = 0; - instance->tx_pos = 0; - return instance; -} - -static void subghz_protocol_encoder_allstar_firefly_free(void* context) { - furi_assert(context); free(context); -} - -static void subghz_protocol_encoder_allstar_firefly_stop(void* context) { - UNUSED(context); -} - -static SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize( - void* context, FlipperFormat* flipper_format) { - furi_assert(context); - SubGhzProtocolEncoderAllstarFirefly* instance = context; - SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, AF_SYM_COUNT); - if(status != SubGhzProtocolStatusOk) return status; - af_uint64_to_dip(instance->generic.data, instance->dip); - if(!af_dip_valid(instance->dip)) return SubGhzProtocolStatusErrorParserOthers; - instance->tx_size = af_build_tx_buf(instance->dip, instance->tx_buf); - instance->tx_pos = 0; - return SubGhzProtocolStatusOk; -} - -static LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) { - furi_assert(context); - SubGhzProtocolEncoderAllstarFirefly* instance = context; - if(instance->tx_pos >= instance->tx_size) return level_duration_reset(); - bool lv = (instance->tx_pos % 2 == 0); - uint32_t dur = instance->tx_buf[instance->tx_pos++]; - return level_duration_make(lv, dur); -} - -const SubGhzProtocolDecoder subghz_protocol_decoder_allstar_firefly = { - .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 = 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, -}; - -const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly = { - .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_AM | - SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | - SubGhzProtocolFlag_Send, - .decoder = &subghz_protocol_decoder_allstar_firefly, - .encoder = &subghz_protocol_encoder_allstar_firefly, -}; diff --git a/lib/subghz/protocols/allstar_firefly.h b/lib/subghz/protocols/allstar_firefly.h index 6ebf0dda6..96fc718d8 100644 --- a/lib/subghz/protocols/allstar_firefly.h +++ b/lib/subghz/protocols/allstar_firefly.h @@ -31,31 +31,113 @@ * &subghz_protocol_allstar_firefly, (in the protocol array) */ -#include +#include "base.h" /* Protocol name (must match what is written to .sub files) */ -#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly" +#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly" -/* Timing constants (from 37-frame analysis of two captures) */ -#define AF_FREQ 318000000UL -#define AF_BIT_COUNT 9u -#define AF_SYM_COUNT 18u +typedef struct SubGhzProtocolDecoderAllstarFirefly SubGhzProtocolDecoderAllstarFirefly; +typedef struct SubGhzProtocolEncoderAllstarFirefly SubGhzProtocolEncoderAllstarFirefly; -#define AF_LONG_PULSE_US 4045u -#define AF_SHORT_PULSE_US 530u -#define AF_SHORT_GAP_US 607u -#define AF_LONG_GAP_US 4139u -#define AF_INTERFRAME_US 30440u +extern const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder; +extern const SubGhzProtocol subghz_protocol_allstar_firefly; -#define AF_SHORT_PULSE_MIN 300u -#define AF_SHORT_PULSE_MAX 1200u -#define AF_LONG_PULSE_MIN 2500u -#define AF_LONG_PULSE_MAX 5500u -#define AF_FRAME_THRESH_US 20000u -#define AF_TX_REPEAT 20u -#define AF_TX_BUF_SIZE (AF_TX_REPEAT * AF_SYM_COUNT * 2u + 8u) +/** + * 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); -/* Protocol vtable entries */ -extern const SubGhzProtocolDecoder subghz_protocol_decoder_allstar_firefly; -extern const SubGhzProtocolEncoder subghz_protocol_encoder_allstar_firefly; -extern const SubGhzProtocol subghz_protocol_allstar_firefly; +/** + * 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 + */ +uint8_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); From 63d49b6e48533c8a182f3d0af97c59e629f07706 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Apr 2026 03:31:11 +0300 Subject: [PATCH 149/202] subghz upgrades [ci skip] docs update pr template update changelog update --- .github/pull_request_template.md | 20 +- CHANGELOG.md | 7 +- .../main/subghz/helpers/subghz_custom_event.h | 14 ++ .../main/subghz/helpers/subghz_gen_info.c | 153 +++++++++++++- .../main/subghz/helpers/subghz_gen_info.h | 4 +- .../helpers/subghz_txrx_create_protocol_key.c | 6 +- .../helpers/subghz_txrx_create_protocol_key.h | 2 +- .../resources/subghz/assets/keeloq_mfcodes | 190 +++++++++++------- .../subghz/scenes/subghz_scene_set_button.c | 8 +- .../subghz/scenes/subghz_scene_set_counter.c | 12 +- .../subghz/scenes/subghz_scene_set_seed.c | 22 +- .../subghz/scenes/subghz_scene_set_serial.c | 14 +- .../subghz/scenes/subghz_scene_set_type.c | 30 ++- documentation/SubGHzSupportedSystems.md | 56 +++++- lib/nfc/protocols/mf_desfire/mf_desfire_i.c | 4 +- .../mf_ultralight/mf_ultralight_listener.c | 4 + lib/subghz/protocols/came_atomo.c | 56 +++++- lib/subghz/protocols/keeloq.c | 100 ++++++++- lib/subghz/protocols/keeloq_common.c | 94 +++++++++ lib/subghz/protocols/keeloq_common.h | 20 ++ lib/subghz/protocols/public_api.h | 2 +- targets/f7/api_symbols.csv | 2 +- 22 files changed, 673 insertions(+), 147 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 4bc69d8e6..234fbe7c5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -6,8 +6,22 @@ - [ Describe how to verify changes ] -# Checklist (For Reviewer) +# Author Checklist (Fill this out): -- [ ] PR has description of feature/bug -- [ ] Description contains actions to verify feature/bugfix +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation (if it exists) + +# AI usage disclosure (Fill this out): + +- [ ] Partially AI assisted (clarify below which code was AI assisted and briefly explain what it does). +- [ ] Fully AI generated (explain what all the generated code does in moderate detail). + +- [ Describe how AI was used in this PR if it was used ] + +# Checklist (For Reviewer) (Don't fill this out!): + +- [ ] PR has proper description of new feature/bugfix +- [ ] Description contains actions to verify feature/bugfix on the hardware +- [ ] No obvious issues with the code was found - [ ] I've built this code, uploaded it to the device and verified feature/bugfix diff --git a/CHANGELOG.md b/CHANGELOG.md index d432473eb..428c8e8f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ## Main changes -- Current API: 87.6 +- Current API: 87.7 +* SubGHz: Add support for **42+ Keeloq based systems** (with partial Add Manually support) (see [Full list](/documentation/SubGHzSupportedSystems.md)) (by @zero-mega, @xMasterX, ARF Team) * SubGHz: Add **Nord ICE** protocol (33 bits, Static) +* SubGHz: **Better support for CAME Atomo** type remotes (TOPD4REN) (decode + button codes) (thx to Roman for raw recordings) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * SubGHz: **Fix CAME TWEE repeats count for button click** @@ -9,7 +11,8 @@ * NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) * NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) -* Apps: Build tag (**1apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) +* Apps: Build tag (**21apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index ebb120929..63b590bfc 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -69,6 +69,7 @@ typedef enum { SetTypeFaacSLH_868, SetTypeFaacSLH_433, SetTypeBFTMitto, + SetTypeErreka433, SetTypeSomfyTelis, SetTypeSomfyKeytis, SetTypeKingGatesStylo4k, @@ -109,6 +110,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 db3f2926a..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){ @@ -698,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 60212801c..8fcfb92fc 100644 --- a/applications/main/subghz/helpers/subghz_gen_info.h +++ b/applications/main/subghz/helpers/subghz_gen_info.h @@ -7,7 +7,7 @@ typedef enum { GenFaacSLH, GenKeeloq, GenCameAtomo, - GenKeeloqBFT, + GenKeeloqSeed, GenAlutechAt4n, GenSomfyTelis, GenSomfyKeytis, @@ -55,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; 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 11bb70ed9..4601cbc28 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, 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); 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 811724584..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, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 572bda5cc..2772b0e39 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,76 +1,120 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 4E 6F 74 69 63 65 73 20 62 75 6C 67 65 55 77 55 -DD5D658073C2C257430FD30815480D300A4FAA72750B741431D90E7B74C6F94B -D30DBA9B63C4A5A47E6F1462819706F347B4524FB0342729B454791ECB491338 -4BF3E836BECF53BF8ECADC9E35EB94A6F2A424C4918226B7B2F3B0102BB28AFB61610BF188269855559DDEF49413635B -96BD4E3E3D02094DFE3C4E5CCBC8ED7BE679ACE3C83ADF1433BA4E08576E610D -99B4D5475099E999558BBBCFC5083B08BB99FF7F9E8EFEB222388750CBA464CD -DA94EF9FED763E6625C2FA839C8A288986664EFD12575D21575B6C77345EC6A2 -FD0A76366C8F84C2BF080A40EB6573CF41AFA51138320C187BDDB1E40CEA35E8 -5C375767AB4BE93E3A10875B4B006D4264FD9FB606381A8878AF4EC960EFC849 -01297BF2D4372C5FC376E79835D8D5A382F76E3D9367E2FA81F54C10BDB2FDA8 -415FE673D1CDAE0E7141EA78326573987E51353C91992E334FCD030E3172B8C6 -C6EA321FA06DBD86D328B5CBBCFE94628DA3D415463835229F1224EAB5F8B181 -6468B83FB26298BD496299D64DAE0F2D12B081CDE9B83AA5B19500162D7DA6E8 -2569E458EFFE0725581F7490CBF416B3E8E64C1D0F2C13DEA5BE926475F631DC -857A04B029277DA403276CADE88EAD4956A959AB9C652614F435DCE1B3B92215 -A08B60A323178B3A8644D0018C705E5E13F5F34DB41132348CEDBBCB6C75164F -A171F6CEEB240D1117DF8A5396B4E468EB03D8AA315321FA19F01E67145A861E -4255FAC9CD843A206D89D6D5204409193E594AE73D5E490F84F39DC76A0C6C23 -589E6270609485D8FEA3029A23C3084F48FB5384939ACF447CA289C4282F38BA -D1B349CF3C402BCF7F87FEF5A19E8C2748D52715092EC5668C3C61A15958F7F2 -34191142584299F22C0A007EC8E333646E57C6B406C103795ED4A2731CAF55A5 -158809D80C052D780C8AA05F88AF67FF4CF93234A7F7A78B5AA11F067623AD2D -81C22401A96674BB64D3512AEEB5F8A060537AFE1D4F2BB31ADB237E1BC61FC0 -C732F26FEC449F545CD379BF9AD2AC2769EDA98C0D7163A69E37EE17853E98AF -59375DCD3703B87223DE49174274CD28FF95BA077357A10304FC3B44E679CA91 -E9E9E87E1098CCB6C3799EB31D91E9C77EF243164FE98DAA688B22CD8ED7D944 -AC0319E2F9D0520357E61B88B17D293AE32B68661484890AEBCBADA0E7C47C40 -865F354CA28D9AABFFB3ABBD15CB22ECBDC937D292AA2D72B9D7457E870E9117 -7D2D90D3D583F77F60C8782E61011D52A452FC33F8B5E6A8AD6A2378F2819D3B -FC3C20DA524C0E1D43881F15605E18B58D9E804D15F01AB3DC9850D472C4C3A1 -25C03EAA292F03155ACD4C599030C159C2F085970B2BD905A6CF42E69D5E7BF4 -F84099879C1BD93BD77489CBEE9DB22F7F4688480A5DB9F1CF8359ADAA456B10 -21A4BE7E01B1848FB26E7CFEAD9FA841F4B80C4C2C84D07F6737F61E1FB4F4F3 -8094BD23328B5854C11FE696D21F1F58C280DC6D31F7F6B56F99E04D83949EFD -B87664A507982A6103904E81BF7D6AE1222E038D405EE84F3B5E84F4E3D6BFC7 -F102B622433FCBA3910DA43309D67FC46BE7A472BAAF8CDDEFFCD796D23DB122 -D3A015CE7E7D9CD23CED808CC72CBB42A09C5D718B58C04E1E25B3D7F46BCBB3 -02E1C8124D6B955689193369C87BDFCC66C0750E65EED7D7592E6EECA83BF1CA -4A37AC36B48DF89D664CC0390F2B1AAF553EBC2C661D14E5DDA2016C66946FF6 -3087AB22BF5A72376A888C26846B15DF842E70F8AE113E64BC855B2BA4801831 -562ED92C172D0B20799D0FDC565AED5B2A771249ACBA7D8F33176B99B3C4CD88 -448D6DEAC1C01D1B96EBB480D22A5AD011DC36655D8318BACCEE630F9BA5CC20 -2F9C204D0A2E7A5686F7684E438DC3DCEE400AE23306D6B604A5905DB33DBF0F -50A7E7773F4BCA75247E2E9ACF9025F5DF0370A2C20E2ECE8EDC00F25761D158 -2F2B0A5F9A71FD2B1E2AA55B3C6EA75789FC5A20CE3E8790E0260E59D10E7D96 -EBBD9E861DCC0A85A2828026C98D69F882F05CE2DE0CD58AB06FE2E778CBEABC40E591AE59EEA3A4012F9266E61D4971 -8716AE40A23BB9D23DC9EE0917D7E2D50891A4D8495F08174953A883B4EA61DA -F2B9B78FA7239BB61E4FB5DDA4286C5FA2CFF34A4456781C55AC4E23644E2A46 -D570A2BD25921E6E3409E0017BC9547F1BFF0992EDC35F0BC0C526B03738D8F0 -BDA88E42BAA3025E730AF8244F2F0DC5E8F9B531882A5E1EF48BF6F0B86DB186 -03E2FFCDF9243D7DB19DAEAF2563D2CF604D82E73E501071523EA1DD3E3A1313 -702A8F9F8F5D670D999D2D4F6388F22846B23FFC4EB53A71DAC92BED76A7D1111C0536FA5293B148B501E8BE70F84C3F -A605F5B456CD733EAD4E12432617936494C1ACA42B2CEE639151AD8846D02EFBF17575565A458B7E8D6E2929BEE70ADB -F5628A7A8602546DD4BAB166DF796B7011A3F66F896599FFC7467045B1B3EB4F -BE6B94C4A7C701FBF7D189FA3C371DC7D208CF84E1EEC00A4C2F4DC14BF8CC17 -AFF0D5E080A7DAD2AFE0C498D3DFD58EAA0AE4F4F96883B46C36537B66D344C2 -E0435324027761281E17E62CCDB964E7B87EA673205DC76109570DEEA25E9C86 -84EB0F58B6F3ED2B6D62BA21D0EBCA0A1C7E17C792EBB47A2EA596AB6E4E98A78908E5E31CCE35CD8091F8D39DA52EF5 -C4027456DE3BF67B080AC563B2F38318B04DB5ED968D33D7493F0FC5CC51B165 -7C59386D2F5AE8F14AA3C1FBD953F5B7B153A3B94B20BA51C18B18BF2F0DB037 -A8E7A91824FA42085CEEE47A80A9269779AE69AFADFA210E8A4C48585F827FCB99509686D7C62215E556DBE73BB476B5 -514F6030D39CC0DE60B0280EC55F32CE93CA5D9446E7DDF03949AAB7C1276CA036A65A5B58C0904158117768D0AA22A6 -F75FC0AA714F37B75E2FDAFB837B671CD7A349A8D77565C6C0F2943A4B0B88A8 -E8C43768DA5C853253C9FD9D2112A66C509DAA37601CBD83DBFA59329E015D40 -2006DA52D75063F76A99A726E0E7B21A737A7D759C470867FF7EECC61B6A5637 -5B474E6EC7E10EB5415B5593E2C03ED5C079B2CFE2B7BCBC8B3D758727043CD4 -1F3328F14AE9C0867143929D0020AF17F58D09C1B47DBD92FFEAFD2260779DFD -CB194E675F700497AA04686CB17CF77F4295D095A0500D41FD0DC2C4FD8BE294 -328E577B4A997108194BEE91E68190663C4AA3E142B8BEF2A99B70CB4D11897DAA990D5F1FC3D33098982D73F63A26DA -4D5DC5AEF974F0E15A0B06726AC0D2589B1C931B848BFD15BFFE01B8F3BF8E50 -A9B1734BCB160609DF5B02E3C9E459D2DE3297920CBAA7677692C27886527D82 -A161A69B3BE5975F8742AE5461A4983201D7B6B91978D87C0E0D71BF74F5A6D7 -FE3F0F9875D8D2C6E51923E610A812E6BAC4E65FCD4026AA965600A4235AF3EE03D27B793C4E629948BADA5C44EE8915 +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_set_button.c b/applications/main/subghz/scenes/subghz_scene_set_button.c index 2006fdfa6..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; @@ -99,7 +99,7 @@ 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: diff --git a/applications/main/subghz/scenes/subghz_scene_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index be13499b8..7a5da91c3 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; @@ -123,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 = @@ -168,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: diff --git a/applications/main/subghz/scenes/subghz_scene_set_seed.c b/applications/main/subghz/scenes/subghz_scene_set_seed.c index b3bd447e6..482ad07e4 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -22,9 +22,9 @@ 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: @@ -78,17 +78,17 @@ 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: diff --git a/applications/main/subghz/scenes/subghz_scene_set_serial.c b/applications/main/subghz/scenes/subghz_scene_set_serial.c index ba105a804..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; @@ -118,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 = @@ -172,7 +172,7 @@ 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: diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 7ee72ea21..f144947d7 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -15,6 +15,7 @@ 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", @@ -56,6 +57,19 @@ 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: Clemsa Mutancode 433MHz", + [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 TX4RC 433MHz", @@ -163,16 +177,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( @@ -313,7 +327,7 @@ 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) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 87615f2b0..b6f1b34f0 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -22,7 +22,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - BETT `433.92MHz` `AM650` (18 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) -- CAME Atomo `433.92MHz, 868MHz` `AM650` (62 bits, Dynamic) +- 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)) @@ -33,7 +33,7 @@ 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, 868.35MHz` `AM650` (64 bits, Dynamic) (+ Genius KILO TX2/4 JLC) +- 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) @@ -120,7 +120,7 @@ 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) @@ -128,6 +128,47 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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 (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (receiver checks for 10bit only (unverified)) +- Erreka - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - secure learning with Seed) +- 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,10 +204,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) +- 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/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_listener.c b/lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.c index d8c5fb1e6..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); diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index bd21dafd1..ddab59ff3 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -258,6 +258,10 @@ 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 @@ -482,9 +486,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 @@ -661,6 +667,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]; @@ -671,7 +681,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) { @@ -738,6 +748,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; @@ -756,6 +772,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; @@ -774,6 +796,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; diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 2bf173994..ac04610f9 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -367,7 +367,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) || @@ -376,20 +376,33 @@ static bool subghz_protocol_keeloq_gen_data( (strcmp(instance->manufacture_name, "Pecinin") == 0) || (strcmp(instance->manufacture_name, "Steelmate") == 0) || (strcmp(instance->manufacture_name, "Cardin_S449") == 0) || - (strcmp(instance->manufacture_name, "Stilmatic") == 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( @@ -453,6 +466,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( @@ -511,7 +546,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, @@ -1101,6 +1136,55 @@ static uint32_t subghz_protocol_keeloq_check_remote_controller_selector( 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: // Simple Learning decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key); @@ -1625,7 +1709,9 @@ void subghz_protocol_decoder_keeloq_get_string(void* context, FuriString* output code_found_reverse_lo, instance->generic.btn, instance->manufacture_name); - } else if(strcmp(instance->manufacture_name, "BFT") == 0) { + } 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; 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 1250cd2fc..93904f194 100644 --- a/lib/subghz/protocols/keeloq_common.h +++ b/lib/subghz/protocols/keeloq_common.h @@ -28,6 +28,10 @@ // #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 @@ -101,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/public_api.h b/lib/subghz/protocols/public_api.h index 43bef118b..aa5e1965a 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, diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index bccd2c381..c1c6cbcef 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3734,7 +3734,7 @@ 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_jarolift_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, 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_keeloq_seed_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, uint32_t, const char*, SubGhzRadioPreset*" Function,+,subghz_protocol_keeloq_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_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_nice_flor_s_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, SubGhzRadioPreset*, _Bool" From ad2568cd12cd256817c55ccf164dbb5ff0fc6331 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 21 Apr 2026 03:57:01 +0300 Subject: [PATCH 150/202] upd changelog --- CHANGELOG.md | 1 + documentation/SubGHzSupportedSystems.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 428c8e8f4..520aefaa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Main changes - Current API: 87.7 * SubGHz: Add support for **42+ Keeloq based systems** (with partial Add Manually support) (see [Full list](/documentation/SubGHzSupportedSystems.md)) (by @zero-mega, @xMasterX, ARF Team) +* SubGHz: Add **Allstar Firefly 318ALD31K** protocol (18 bits, Static) (PR #989 | by @jlaughter) * SubGHz: Add **Nord ICE** protocol (33 bits, Static) * SubGHz: **Better support for CAME Atomo** type remotes (TOPD4REN) (decode + button codes) (thx to Roman for raw recordings) * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index b6f1b34f0..8bb37d9f9 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -58,6 +58,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - 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. From 1ed18e469c2199f66d6efe8ad913a07094424fd3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 23 Apr 2026 04:51:24 +0300 Subject: [PATCH 151/202] upd changelog, fix small logic issue --- CHANGELOG.md | 3 ++- lib/subghz/protocols/kinggates_stylo_4k.c | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 520aefaa3..ce976953e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,8 +13,9 @@ * NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**21apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**23apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state)

diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 29922c10e..ef2bd5dc8 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -568,8 +568,8 @@ static void subghz_protocol_kinggates_stylo_4k_remote_controller( if(((decrypt >> 28) == instance->btn) && (((decrypt >> 24) & 0x0F) == 0x0C) && (((decrypt >> 16) & 0xFF) == (instance->serial & 0xFF))) { ret = true; - break; } + break; } } if(ret) { From c4ef54c71afa5fb892b0f84d1022333b70eafce2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:03:47 +0300 Subject: [PATCH 152/202] Fix CLI with NTAG4xx and Type 4 Tag support by WillyJL --- applications/main/nfc/application.fam | 4 ++- .../cli/commands/dump/nfc_cli_command_dump.c | 24 ++++++++------ .../protocols/ntag4xx/nfc_cli_dump_ntag4xx.c | 31 +++++++++++++++++++ .../protocols/ntag4xx/nfc_cli_dump_ntag4xx.h | 5 +++ .../type_4_tag/nfc_cli_dump_type_4_tag.c | 31 +++++++++++++++++++ .../type_4_tag/nfc_cli_dump_type_4_tag.h | 5 +++ .../nfc/cli/commands/helpers/nfc_cli_format.c | 4 ++- .../cli/commands/raw/nfc_cli_command_raw.c | 3 ++ 8 files changed, 96 insertions(+), 11 deletions(-) create mode 100644 applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/ntag4xx/nfc_cli_dump_ntag4xx.h create mode 100644 applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.c create mode 100644 applications/main/nfc/cli/commands/dump/protocols/type_4_tag/nfc_cli_dump_type_4_tag.h diff --git a/applications/main/nfc/application.fam b/applications/main/nfc/application.fam index 9e5caf8df..7da31a971 100644 --- a/applications/main/nfc/application.fam +++ b/applications/main/nfc/application.fam @@ -631,13 +631,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) { From cc84f535ed667370860e03a42b1a83c5cd05fe19 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:04:41 +0300 Subject: [PATCH 153/202] remove unused subghzhistory code --- applications/main/subghz/subghz_history.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index 20d73feb0..ba116cc26 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -231,13 +231,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"); From 893feaff20d608e3706e3b20137fc702c61b984a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:28:49 +0300 Subject: [PATCH 154/202] upd changelog --- CHANGELOG.md | 3 ++- documentation/SubGHzSupportedSystems.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce976953e..5af7fc2f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ * NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) * NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) * NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) +* NFC: Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**23apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**24apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) diff --git a/documentation/SubGHzSupportedSystems.md b/documentation/SubGHzSupportedSystems.md index 8bb37d9f9..7bef46f65 100644 --- a/documentation/SubGHzSupportedSystems.md +++ b/documentation/SubGHzSupportedSystems.md @@ -22,6 +22,7 @@ That list is only for default SubGHz app, apps like *Weather Station* have their - BETT `433.92MHz` `AM650` (18 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) @@ -129,7 +130,6 @@ The following manufacturers have KeeLoq support in Unleashed firmware: - 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 (R-Tech) - `433.92MHz` `AM650` (KeeLoq, 64 bits) (12bit serial part in Hop - normal learning) (receiver checks for 10bit only (unverified)) -- Erreka - `433.92MHz` `AM650` (KeeLoq, 64 bits) (8bit serial part in Hop - secure learning with Seed) - 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) From fb1a80fe894be736dbc4a17e827cb9c76d369a63 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 24 Apr 2026 01:54:33 +0300 Subject: [PATCH 155/202] small ui fixes to fit text --- applications/main/subghz/scenes/subghz_scene_set_type.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index f144947d7..4f0bd3685 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -66,13 +66,13 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeWisniowski433] = "KL: Wisniowski 433MHz", [SetTypeFadini433] = "KL: Fadini 433MHz", [SetTypeMc_Garcia433] = "KL: Mc Garcia 433MHz", - [SetTypeClemsa_Mutancode433] = "KL: Clemsa Mutancode 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 TX4RC 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", From a9f5bace13c396b20098c3ba977c09fe6fa8c4a2 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 25 Apr 2026 00:01:00 +0300 Subject: [PATCH 156/202] fix nice flo --- CHANGELOG.md | 1 + lib/subghz/protocols/nice_flo.c | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af7fc2f4..03ddaa69d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol * SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning * SubGHz: **Fix CAME TWEE repeats count for button click** +* SubGHz: Improve Nice FLO decoding (thx to Roman for raw recordings) * NFC: Add **ISO15693-3 and SLIX write-back support** (PR #984 | by @DoniyorI) * NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) * NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index f7c8fe757..b0acfd1eb 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, }; @@ -211,8 +211,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; @@ -326,8 +326,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, From 44f025d119df744ea662f6db80b0294721683410 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 25 Apr 2026 04:15:31 +0300 Subject: [PATCH 157/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03ddaa69d..ebddaf63c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ * NFC: Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**24apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**25apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) From f1e1d0131385c029ba17a5dfeb9251083a38f115 Mon Sep 17 00:00:00 2001 From: SkeletonMan03 Date: Sun, 26 Apr 2026 14:11:14 -0500 Subject: [PATCH 158/202] Make toggle more obvious --- applications/services/desktop/views/desktop_view_lock_menu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/services/desktop/views/desktop_view_lock_menu.c b/applications/services/desktop/views/desktop_view_lock_menu.c index 995a47f95..1dac8fe7a 100644 --- a/applications/services/desktop/views/desktop_view_lock_menu.c +++ b/applications/services/desktop/views/desktop_view_lock_menu.c @@ -65,9 +65,9 @@ void desktop_lock_menu_draw_callback(Canvas* canvas, void* model) { //if(i == DesktopLockMenuIndexLock) { if(i == DesktopLockMenuIndexBt) { if(m->bt_mode) { - str = "Bluetooth Off"; + str = "Turn Bluetooth Off"; } else { - str = "Bluetooth On"; + str = "Turn Bluetooth On"; } } else if(i == DesktopLockMenuIndexStealth) { if(m->stealth_mode) { From ceddafdc690b2a12e1204edf7a37c557f9e63682 Mon Sep 17 00:00:00 2001 From: SkeletonMan03 Date: Sun, 26 Apr 2026 14:51:28 -0500 Subject: [PATCH 159/202] Change power off menu to say "Battery" instead of "Settings" --- .../power_settings_app/scenes/power_settings_scene_power_off.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c index b89c2d0b8..eda825d6d 100644 --- a/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c +++ b/applications/settings/power_settings_app/scenes/power_settings_scene_power_off.c @@ -27,7 +27,7 @@ void power_settings_scene_power_off_on_enter(void* context) { dialog, " I will be\nwaiting for\n you here...", 78, 14, AlignLeft, AlignTop); dialog_ex_set_icon(dialog, 24, 10, &I_dolph_cry_49x54); } - dialog_ex_set_left_button_text(dialog, "Settings"); + dialog_ex_set_left_button_text(dialog, "Battery"); dialog_ex_set_right_button_text(dialog, "Power Off"); dialog_ex_set_result_callback(dialog, power_settings_scene_power_off_dialog_callback); dialog_ex_set_context(dialog, app); From ef1a4495183872b8e934c86e2e35729046cb4c87 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 27 Apr 2026 00:43:06 +0300 Subject: [PATCH 160/202] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebddaf63c..c15357ce8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) * Apps: Build tag (**25apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* UI: Wording changes to be more clear (PR #994 | by @SkeletonMan03) * SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state) From 637258ffbb08801f1a7675b8a81ad2a3c26b17fb Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:03:51 +0300 Subject: [PATCH 161/202] Changing zoom global mic shortcut to avoid conflict with terminal --- applications/system/hid_app/views/hid_ptt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 8a28cd292..e9b1849a0 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -128,9 +128,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) { From fe66188541eb56a1a6d283b52c0e1d35413e7b07 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:38:23 +0300 Subject: [PATCH 162/202] Adding status bar --- applications/system/hid_app/views/hid_ptt.c | 115 +++++++++++++++----- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index e9b1849a0..b688de89c 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -1,8 +1,11 @@ #include "hid_ptt.h" #include "hid_ptt_menu.h" #include +#include #include #include +#include +#include #include "../hid.h" #include "../views.h" @@ -673,43 +676,94 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } +static void hid_ptt_draw_status_bar(Canvas* canvas, 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, 11); + canvas_set_color(canvas, ColorBlack); + canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); + canvas_draw_line(canvas, 1, 9, 62, 9); + + if(connected) { + canvas_draw_icon(canvas, 1, 0, &I_Ble_connected_15x15); + } else { + canvas_draw_icon(canvas, 1, 0, &I_Ble_disconnected_15x15); + } + + const uint8_t battery_x = 47; + const uint8_t battery_y = 2; + 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, 31, 8, 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); - } -#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 top_offset = 11; + const uint8_t helper_top_y = 92; 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 = labels_center_y - 5; + const uint8_t os_label_y = app_label_y + 11; + + // Header + canvas_set_font(canvas, FontPrimary); +#ifdef HID_TRANSPORT_BLE + hid_ptt_draw_status_bar(canvas, model->connected); +#else + hid_ptt_draw_status_bar(canvas, false); +#endif + + // OS and App labels + canvas_set_font(canvas, FontSecondary); + 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, 7, 111, &I_Hold_15x5); + canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9); + canvas_draw_icon(canvas, 34, 112, &I_for_help_27x5); + canvas_draw_icon(canvas, 0, 119, &I_Help_exit_64x9); + canvas_draw_icon(canvas, 24, 119, &I_BtnBackV_9x9); + // Up canvas_draw_icon(canvas, x_2, y_1, &I_Button_18x18); if(model->up_pressed) { @@ -756,23 +810,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 = top_offset; + 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); } } From 74c69f74eabcd658f05771f576543bcb96155af8 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 21:50:59 +0300 Subject: [PATCH 163/202] Fixing text position --- applications/system/hid_app/views/hid_ptt.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index b688de89c..0c9ed837b 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -726,6 +726,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { HidPushToTalkModel* model = context; const uint8_t top_offset = 11; + const uint8_t status_bar_bottom_y = top_offset; const uint8_t helper_top_y = 92; const uint8_t x_1 = 0; @@ -738,7 +739,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { 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 = labels_center_y - 5; + const uint8_t app_label_y = labels_center_y - 1; const uint8_t os_label_y = app_label_y + 11; // Header @@ -810,7 +811,7 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Back / Mic const uint8_t x_mic = x_3; - const uint8_t y_mic = top_offset; + 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))) { From a9438319d60d11b852eb595961d3b0c26b0938c2 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:08:10 +0300 Subject: [PATCH 164/202] Moving BT icon to status bar --- applications/system/hid_app/views/hid_ptt.c | 24 +++++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 0c9ed837b..f4f476e31 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -676,7 +676,19 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_status_bar(Canvas* canvas, bool connected) { +static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { + // Home-like compact Bluetooth glyph with activity line and end dot. + canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); + canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); + canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + + canvas_draw_line(canvas, x + 6, y + 4, x + 12, y + 4); + canvas_draw_disc(canvas, x + 14, y + 4, 1); +} + +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); @@ -703,10 +715,8 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool connected) { canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); canvas_draw_line(canvas, 1, 9, 62, 9); - if(connected) { - canvas_draw_icon(canvas, 1, 0, &I_Ble_connected_15x15); - } else { - canvas_draw_icon(canvas, 1, 0, &I_Ble_disconnected_15x15); + if(show_bt && connected) { + hid_ptt_draw_bt_connected(canvas, 3, 1); } const uint8_t battery_x = 47; @@ -745,9 +755,9 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // Header canvas_set_font(canvas, FontPrimary); #ifdef HID_TRANSPORT_BLE - hid_ptt_draw_status_bar(canvas, model->connected); + hid_ptt_draw_status_bar(canvas, true, model->connected); #else - hid_ptt_draw_status_bar(canvas, false); + hid_ptt_draw_status_bar(canvas, false, false); #endif // OS and App labels From 3457a32621ef05aa5edd961fed789cd7a9423690 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:23:53 +0300 Subject: [PATCH 165/202] Fixing bt status --- applications/system/hid_app/views/hid_ptt.c | 35 ++++++++++++++------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index f4f476e31..999888641 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -679,15 +679,24 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { // Home-like compact Bluetooth glyph with activity line and end dot. canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); - canvas_draw_line(canvas, x + 6, y + 4, x + 12, y + 4); + canvas_draw_line(canvas, x + 7, y + 4, x + 12, y + 4); canvas_draw_disc(canvas, x + 14, y + 4, 1); } +static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { + // Home-like disconnected icon: only Bluetooth glyph. + canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); + canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); + canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); + canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); +} + static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; @@ -710,17 +719,21 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected } canvas_set_color(canvas, ColorWhite); - canvas_draw_box(canvas, 0, 0, 64, 11); + canvas_draw_box(canvas, 0, 0, 64, 13); canvas_set_color(canvas, ColorBlack); - canvas_draw_rframe(canvas, 0, 0, 64, 11, 1); - canvas_draw_line(canvas, 1, 9, 62, 9); + canvas_draw_rframe(canvas, 0, 0, 64, 13, 1); + canvas_draw_line(canvas, 1, 11, 62, 11); - if(show_bt && connected) { - hid_ptt_draw_bt_connected(canvas, 3, 1); + if(show_bt) { + if(connected) { + hid_ptt_draw_bt_connected(canvas, 3, 2); + } else { + hid_ptt_draw_bt_idle(canvas, 3, 2); + } } const uint8_t battery_x = 47; - const uint8_t battery_y = 2; + 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); @@ -728,14 +741,14 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected 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, 31, 8, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 31, 9, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkModel* model = context; - const uint8_t top_offset = 11; + const uint8_t top_offset = 13; const uint8_t status_bar_bottom_y = top_offset; const uint8_t helper_top_y = 92; From 0f6a6d1a52659d0eec9f2f9b258a83255745110c Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:27:41 +0300 Subject: [PATCH 166/202] Fixing clock in status bar --- applications/system/hid_app/views/hid_ptt.c | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 999888641..b465d7848 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -676,25 +676,28 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { - // Home-like compact Bluetooth glyph with activity line and end dot. +static void hid_ptt_draw_bt_glyph(Canvas* canvas, uint8_t x, uint8_t y) { + // Compact 5x8 Bluetooth rune. canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); +} - canvas_draw_line(canvas, x + 7, y + 4, x + 12, y + 4); - canvas_draw_disc(canvas, x + 14, y + 4, 1); +static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { + // Connected icon: rune + activity pixels/line + end circle. + hid_ptt_draw_bt_glyph(canvas, x, y); + + canvas_draw_dot(canvas, x + 6, y + 4); + canvas_draw_dot(canvas, x + 8, y + 4); + canvas_draw_line(canvas, x + 10, y + 4, x + 12, y + 4); + canvas_draw_circle(canvas, x + 14, y + 4, 1); } static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { - // Home-like disconnected icon: only Bluetooth glyph. - canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); - canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); + // Disconnected icon: only Bluetooth rune. + hid_ptt_draw_bt_glyph(canvas, x, y); } static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { @@ -741,7 +744,7 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected 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, 31, 9, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 31, 10, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { From a8de70b08d5ecbc8705173e6fd4dfe139444836c Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:40:02 +0300 Subject: [PATCH 167/202] Fixing bt icon and crash on bt disconnect --- applications/system/hid_app/views/hid_ptt.c | 40 ++++++++------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index b465d7848..92e2afc9c 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -13,6 +13,18 @@ #define TAG "HidPushToTalk" +// 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; @@ -676,30 +688,6 @@ static void hid_ptt_draw_text_centered(Canvas* canvas, uint8_t y, FuriString* st furi_string_free(disp_str); } -static void hid_ptt_draw_bt_glyph(Canvas* canvas, uint8_t x, uint8_t y) { - // Compact 5x8 Bluetooth rune. - canvas_draw_line(canvas, x + 1, y + 0, x + 1, y + 7); - canvas_draw_line(canvas, x + 1, y + 0, x + 4, y + 2); - canvas_draw_line(canvas, x + 1, y + 7, x + 4, y + 5); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 1); - canvas_draw_line(canvas, x + 1, y + 4, x + 4, y + 7); -} - -static void hid_ptt_draw_bt_connected(Canvas* canvas, uint8_t x, uint8_t y) { - // Connected icon: rune + activity pixels/line + end circle. - hid_ptt_draw_bt_glyph(canvas, x, y); - - canvas_draw_dot(canvas, x + 6, y + 4); - canvas_draw_dot(canvas, x + 8, y + 4); - canvas_draw_line(canvas, x + 10, y + 4, x + 12, y + 4); - canvas_draw_circle(canvas, x + 14, y + 4, 1); -} - -static void hid_ptt_draw_bt_idle(Canvas* canvas, uint8_t x, uint8_t y) { - // Disconnected icon: only Bluetooth rune. - hid_ptt_draw_bt_glyph(canvas, x, y); -} - static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected) { char time_str[16]; DateTime dt; @@ -729,9 +717,9 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected if(show_bt) { if(connected) { - hid_ptt_draw_bt_connected(canvas, 3, 2); + canvas_draw_bitmap(canvas, 3, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); } else { - hid_ptt_draw_bt_idle(canvas, 3, 2); + canvas_draw_bitmap(canvas, 3, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); } } From 3a6d7173f4909b9fc8f0bde024dcac3c1657bf1d Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 22:45:13 +0300 Subject: [PATCH 168/202] Fixing overlapping icons in the statusbar --- applications/system/hid_app/views/hid_ptt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 92e2afc9c..a72a2420a 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -717,13 +717,13 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected if(show_bt) { if(connected) { - canvas_draw_bitmap(canvas, 3, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); + canvas_draw_bitmap(canvas, 2, 2, 16, 8, hid_ptt_bluetooth_connected_16x8_bits); } else { - canvas_draw_bitmap(canvas, 3, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); + canvas_draw_bitmap(canvas, 2, 2, 5, 8, hid_ptt_bluetooth_idle_5x8_bits); } } - const uint8_t battery_x = 47; + const uint8_t battery_x = 48; const uint8_t battery_y = 3; const uint8_t battery_w = 13; const uint8_t battery_h = 6; @@ -732,7 +732,7 @@ static void hid_ptt_draw_status_bar(Canvas* canvas, bool show_bt, bool connected 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, 31, 10, AlignCenter, AlignBottom, time_str); + canvas_draw_str_aligned(canvas, 33, 10, AlignCenter, AlignBottom, time_str); } static void hid_ptt_draw_callback(Canvas* canvas, void* context) { From 02d8a12924f5bfa0145412cd41d386b2ceffe90e Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:03:16 +0300 Subject: [PATCH 169/202] Moving help to app selection menu --- applications/system/hid_app/views/hid_ptt.c | 154 ++++++++++-------- .../system/hid_app/views/hid_ptt_menu.c | 40 ++++- .../system/hid_app/views/hid_ptt_menu.h | 12 ++ 3 files changed, 133 insertions(+), 73 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index a72a2420a..27b1d211f 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -402,6 +402,83 @@ 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\n"; + break; + } + 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" + "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); + 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, @@ -609,67 +686,9 @@ static void hid_ptt_menu_callback( } } - 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); } @@ -771,11 +790,6 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // 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, 7, 111, &I_Hold_15x5); - canvas_draw_icon(canvas, 24, 109, &I_BtnLeft_9x9); - canvas_draw_icon(canvas, 34, 112, &I_for_help_27x5); canvas_draw_icon(canvas, 0, 119, &I_Help_exit_64x9); canvas_draw_icon(canvas, 24, 119, &I_BtnBackV_9x9); @@ -935,11 +949,6 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeLong && event->key == InputKeyRight) { 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); } //LED if(!model->muted || (model->ptt_pressed)) { @@ -971,9 +980,9 @@ 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) { @@ -1204,9 +1213,10 @@ 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; } diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 074c85ba7..439ca4732 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -10,6 +10,8 @@ struct HidPushToTalkMenu { View* view; Hid* hid; + PushToTalkMenuLongOkCallback long_ok_callback; + void* long_ok_callback_context; }; typedef struct { @@ -338,6 +340,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 +410,10 @@ 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); + } return consumed; } diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h index 2defd0569..df6fcb015 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); From 093d611091caef18ba35b765ff1c5817523f21b2 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:17:24 +0300 Subject: [PATCH 170/202] Adding long press left to send enter for zoom --- applications/system/hid_app/views/hid_ptt.c | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 27b1d211f..9e566fb6e 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -445,8 +445,21 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { "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"; + "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( @@ -455,11 +468,13 @@ static void hid_ptt_trigger_mute_linux_gather(HidPushToTalk* hid_ptt) { "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); + 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); } @@ -949,6 +964,14 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { } else if(event->type == InputTypeLong && event->key == InputKeyRight) { model->muted = !model->muted; notification_message(hid_ptt->hid->notifications, &sequence_single_vibro); + } else if(event->type == InputTypeLong && event->key == InputKeyLeft) { + if( + model->appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + 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)) { From dfd5867ed4a731ac0ef26e86e155267b95e9d0e4 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:29:19 +0300 Subject: [PATCH 171/202] Fixing lower helper for non-zoom apps --- applications/system/hid_app/views/hid_ptt.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 9e566fb6e..d26819aad 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -775,7 +775,14 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { const uint8_t top_offset = 13; const uint8_t status_bar_bottom_y = top_offset; - const uint8_t helper_top_y = 92; + + // 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) { + helper_top_y = 92; + } const uint8_t x_1 = 0; const uint8_t x_2 = x_1 + 19 + 4; From 0f877005e389b4ef1e43e39ffaab18bac31f5380 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:44:26 +0300 Subject: [PATCH 172/202] Fixing helper view for zoom --- applications/system/hid_app/assets/for_11x5.png | Bin 0 -> 335 bytes applications/system/hid_app/views/hid_ptt.c | 11 +++++++++++ 2 files changed, 11 insertions(+) create mode 100644 applications/system/hid_app/assets/for_11x5.png 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 0000000000000000000000000000000000000000..3ebc4f23c723185c368b7be348c653ef75427955 GIT binary patch literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^+(69A2qYMG-_`yNq!f}pf_xbms?-=58d?|_egTCV zUNA6}8Za=tN?>5Hn!&&zUNC1@pbb!hDaqU2g@N&Im+%rGkG;gx*OmPhi;Re>^xMDZ zSb#zT$%%etKw27zS%DY~yz;AO0U6?+E{-7_*OPbrZ%8a>K75UZ?IKItT}}prjjRjA zbDC!Zm8h1uMwFx^mZVxG7o`Fz1|tI_GhG7{T_cMSBLgdA6Dwm2Z36=<1A{-hrCul+ za`RI%(<(t4Omz(nbPX&+jEt-djjasKAsQZ3nC}H@(16=el9`)YT#}eufNqJol@U}A T_XhP?pdJQKS3j3^P6appIndex == 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) { From b05dad33863b84339e96b2fce38bbeff4193b3d9 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Tue, 28 Apr 2026 23:51:12 +0300 Subject: [PATCH 173/202] Adding helper info to the menu view --- .../system/hid_app/views/hid_ptt_menu.c | 197 ++++++++++++++---- 1 file changed, 160 insertions(+), 37 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 439ca4732..6c8b1fa65 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -2,16 +2,22 @@ #include "hid_ptt.h" #include #include +#include #include "../hid.h" #include "../views.h" +#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; + PushToTalkMenuLongOkCallback long_ok_callback; + void* long_ok_callback_context; + FuriTimer* hint_timer; }; typedef struct { @@ -62,8 +68,71 @@ 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); @@ -96,7 +165,12 @@ 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)); + size_t item_text_width = item_width - (6 * 2); + if((position == model->position) && model->hint_visible) { + // Reserve space for hint near selected item. + item_text_width = item_width / 2; + } + elements_string_fit_width(canvas, disp_str, item_text_width); canvas_draw_str( canvas, @@ -104,6 +178,38 @@ static void 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 text_y = y_offset + (item_position * item_height) + item_height - 4; + const int32_t icon_y = text_y - 8; + const size_t selected_text_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); + const int32_t hint_start_x = 6 + selected_text_w + 3; + const int32_t row_right_x = item_width - 2; + + if(hint_start_x < row_right_x) { + 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 hint_w = prefix_w + 2 + icon_w + 2 + suffix_w; + const size_t available = row_right_x - hint_start_x; + + int32_t scroll_offset = 0; + if(hint_w > available) { + const size_t overflow = hint_w - available; + 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 = hint_start_x - scroll_offset; + canvas_draw_str(canvas, draw_x, text_y, hint_prefix); + const int32_t icon_x = draw_x + prefix_w + 2; + canvas_draw_icon(canvas, icon_x, icon_y, &I_Ok_btn_9x9); + canvas_draw_str(canvas, icon_x + icon_w + 2, text_y, hint_suffix); + } + } + furi_string_free(disp_str); } @@ -340,38 +446,38 @@ 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_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; - } +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); @@ -410,10 +516,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); - } + } 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; } @@ -430,6 +541,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, @@ -438,6 +554,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; @@ -445,6 +566,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, From 65a12073685a7d32e33b8ab0d349821690278ff6 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:00:02 +0300 Subject: [PATCH 174/202] Improving helper display in menu --- .../system/hid_app/views/hid_ptt_menu.c | 64 +++++++++---------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 6c8b1fa65..7072eff57 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -165,49 +165,43 @@ static void FuriString* disp_str; disp_str = furi_string_alloc_set(PushToTalkMenuItemArray_cref(it)->label); - size_t item_text_width = item_width - (6 * 2); - if((position == model->position) && model->hint_visible) { - // Reserve space for hint near selected item. - item_text_width = item_width / 2; - } - elements_string_fit_width(canvas, disp_str, item_text_width); - - canvas_draw_str( - canvas, - 6, - y_offset + (item_position * item_height) + item_height - 4, - furi_string_get_cstr(disp_str)); + 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; if((position == model->position) && model->hint_visible) { - const char* hint_prefix = "Long press"; + const char* hint_prefix = " | Long press"; const char* hint_suffix = "for help"; - const int32_t text_y = y_offset + (item_position * item_height) + item_height - 4; const int32_t icon_y = text_y - 8; - const size_t selected_text_w = canvas_string_width(canvas, furi_string_get_cstr(disp_str)); - const int32_t hint_start_x = 6 + selected_text_w + 3; - const int32_t row_right_x = item_width - 2; - if(hint_start_x < row_right_x) { - 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 hint_w = prefix_w + 2 + icon_w + 2 + suffix_w; - const size_t available = row_right_x - hint_start_x; + 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); - int32_t scroll_offset = 0; - if(hint_w > available) { - const size_t overflow = hint_w - available; - 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 size_t group_w = label_w + 2 + prefix_w + 2 + icon_w + 2 + suffix_w; - const int32_t draw_x = hint_start_x - scroll_offset; - canvas_draw_str(canvas, draw_x, text_y, hint_prefix); - const int32_t icon_x = draw_x + prefix_w + 2; - canvas_draw_icon(canvas, icon_x, icon_y, &I_Ok_btn_9x9); - canvas_draw_str(canvas, icon_x + icon_w + 2, text_y, hint_suffix); + 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); From 2c1d9bd9e80e5b2c0eb970b3a7cd31821e34be8a Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:12:01 +0300 Subject: [PATCH 175/202] Visialazing long left press for zoom --- applications/system/hid_app/views/hid_ptt.c | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 045b82f1b..9ae4eed0e 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -6,12 +6,15 @@ #include #include #include +#include #include "../hid.h" #include "../views.h" #include "hid_icons.h" #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. @@ -29,6 +32,7 @@ struct HidPushToTalk { View* view; Hid* hid; Widget* help; + FuriTimer* left_hold_timer; }; typedef void (*PushToTalkActionCallback)(HidPushToTalk* hid_ptt); @@ -47,6 +51,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; @@ -74,6 +79,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); @@ -857,6 +922,10 @@ 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) { @@ -930,6 +999,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) { @@ -953,6 +1028,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; @@ -986,6 +1063,8 @@ static void hid_ptt_process(HidPushToTalk* hid_ptt, InputEvent* event) { 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); @@ -1006,6 +1085,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); @@ -1029,6 +1109,8 @@ static uint32_t hid_ptt_menu_view(void* context) { 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)); @@ -1041,6 +1123,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(); }, @@ -1263,6 +1346,8 @@ HidPushToTalk* hid_ptt_alloc(Hid* hid) { 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, From 2a7e048cd8cb8874c7f265d3f06b554a9fc92af4 Mon Sep 17 00:00:00 2001 From: Roman Belyakovsky Date: Wed, 29 Apr 2026 00:29:30 +0300 Subject: [PATCH 176/202] Fixing google meet global screen --- applications/system/hid_app/views/hid_ptt.c | 49 ++++++++++++++++++--- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 9ae4eed0e..2696c22c4 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -787,6 +787,35 @@ 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; @@ -838,14 +867,18 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { furi_assert(context); HidPushToTalkModel* model = context; + 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) { + if((model->appIndex == HidPushToTalkAppIndexZoom || + model->appIndex == HidPushToTalkAppIndexZoomGlobal) && + !app_label_needs_two_lines) { helper_top_y = 92; } @@ -859,8 +892,8 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { 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 = labels_center_y - 1; - const uint8_t os_label_y = app_label_y + 11; + 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); @@ -872,7 +905,11 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { // OS and App labels canvas_set_font(canvas, FontSecondary); - hid_ptt_draw_text_centered(canvas, app_label_y, model->app); + 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 From 58afca1dbfb910c5f27418fb563db3770ed9c76e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 29 Apr 2026 03:10:15 +0300 Subject: [PATCH 177/202] upd changelog, fbt format --- CHANGELOG.md | 3 +- applications/system/hid_app/views/hid_ptt.c | 225 ++++++++++-------- .../system/hid_app/views/hid_ptt_menu.c | 13 +- .../system/hid_app/views/hid_ptt_menu.h | 12 +- 4 files changed, 136 insertions(+), 117 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c15357ce8..c2a3df2d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,9 @@ * NFC: Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**25apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**29apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* HID App: PTT improvements (PR #996 | by @hryamzik) * UI: Wording changes to be more clear (PR #994 | by @SkeletonMan03) * SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) diff --git a/applications/system/hid_app/views/hid_ptt.c b/applications/system/hid_app/views/hid_ptt.c index 2696c22c4..9670b7c2a 100644 --- a/applications/system/hid_app/views/hid_ptt.c +++ b/applications/system/hid_app/views/hid_ptt.c @@ -6,26 +6,48 @@ #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 +#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, + 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, + 0x00, + 0x04, + 0x0d, + 0x16, + 0x0c, + 0x0c, + 0x16, + 0x0d, + 0x04, }; struct HidPushToTalk { @@ -467,97 +489,96 @@ 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_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_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, @@ -765,10 +786,9 @@ static void hid_ptt_menu_callback( break; } } - }, true); - hid_ptt_populate_help(hid_ptt, appIndex); + hid_ptt_populate_help(hid_ptt, appIndex); view_dispatcher_switch_to_view(hid_ptt->hid->view_dispatcher, HidViewPushToTalk); } @@ -892,7 +912,8 @@ static void hid_ptt_draw_callback(Canvas* canvas, void* context) { 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 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 @@ -960,7 +981,8 @@ 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); + hid_ptt_draw_zoom_enter_hint( + canvas, x_1, y_2 + 18, model->left_hold_progress, model->left_pressed); } // Right / Camera @@ -1097,9 +1119,8 @@ 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) { - if( - model->appIndex == HidPushToTalkAppIndexZoom || - model->appIndex == HidPushToTalkAppIndexZoomGlobal) { + 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); diff --git a/applications/system/hid_app/views/hid_ptt_menu.c b/applications/system/hid_app/views/hid_ptt_menu.c index 7072eff57..ca293160c 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.c +++ b/applications/system/hid_app/views/hid_ptt_menu.c @@ -2,14 +2,13 @@ #include "hid_ptt.h" #include #include -#include #include "../hid.h" #include "../views.h" #include "hid_icons.h" -#define TAG "HidPushToTalkMenu" -#define PTT_MENU_HELP_HINT_DELAY_MS 5000U +#define TAG "HidPushToTalkMenu" +#define PTT_MENU_HELP_HINT_DELAY_MS 5000U #define PTT_MENU_HINT_TIMER_PERIOD_MS 150U struct HidPushToTalkMenu { @@ -97,9 +96,8 @@ static void hid_ptt_menu_hint_timer_callback(void* context) { 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); + 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; @@ -123,8 +121,7 @@ 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)); + 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) { diff --git a/applications/system/hid_app/views/hid_ptt_menu.h b/applications/system/hid_app/views/hid_ptt_menu.h index df6fcb015..ee61a9a50 100644 --- a/applications/system/hid_app/views/hid_ptt_menu.h +++ b/applications/system/hid_app/views/hid_ptt_menu.h @@ -12,12 +12,12 @@ typedef void (*PushToTalkMenuItemCallback)( uint32_t itemIndex, FuriString* itemLabel); - typedef void (*PushToTalkMenuLongOkCallback)( - void* context, - uint32_t listIndex, - FuriString* listLabel, - 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); From bcbb1b5e048923fe1ce139503d80c76ff6aede2f Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Thu, 30 Apr 2026 22:12:59 +0300 Subject: [PATCH 178/202] remove duplicate code from raw protocol --- CHANGELOG.md | 1 + lib/subghz/protocols/raw.c | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a3df2d5..9a194a1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ ## Other changes * HID App: PTT improvements (PR #996 | by @hryamzik) * UI: Wording changes to be more clear (PR #994 | by @SkeletonMan03) +* SubGHz: Fix duplicate code in RAW protocol * SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) * OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) * Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state) diff --git a/lib/subghz/protocols/raw.c b/lib/subghz/protocols/raw.c index 326576bde..483435124 100644 --- a/lib/subghz/protocols/raw.c +++ b/lib/subghz/protocols/raw.c @@ -99,10 +99,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 From daec03bd352db1654c3bf456bbeeed2eb9f87d03 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 3 May 2026 23:08:56 +0300 Subject: [PATCH 179/202] add canvas_buffer to api --- CHANGELOG.md | 3 ++- applications/services/gui/canvas.c | 1 + applications/services/gui/canvas.h | 16 ++++++++++++++++ applications/services/gui/canvas_i.h | 16 ---------------- targets/f7/api_symbols.csv | 6 ++++-- 5 files changed, 23 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a194a1f4..3d26747d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ ## Main changes -- Current API: 87.7 +- Current API: 87.8 * SubGHz: Add support for **42+ Keeloq based systems** (with partial Add Manually support) (see [Full list](/documentation/SubGHzSupportedSystems.md)) (by @zero-mega, @xMasterX, ARF Team) * SubGHz: Add **Allstar Firefly 318ALD31K** protocol (18 bits, Static) (PR #989 | by @jlaughter) * SubGHz: Add **Nord ICE** protocol (33 bits, Static) @@ -17,6 +17,7 @@ * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) * Apps: Build tag (**29apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* UI: Add `canvas_get_buffer`, `canvas_get_buffer_size` to public API * HID App: PTT improvements (PR #996 | by @hryamzik) * UI: Wording changes to be more clear (PR #994 | by @SkeletonMan03) * SubGHz: Fix duplicate code in RAW protocol diff --git a/applications/services/gui/canvas.c b/applications/services/gui/canvas.c index 337789dd3..581bc9312 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 efd314687..3bbdde83d 100644 --- a/applications/services/gui/canvas.h +++ b/applications/services/gui/canvas.h @@ -85,6 +85,22 @@ typedef enum { /** 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/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index c1c6cbcef..3b6b8417d 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,87.7,, +Version,+,87.8,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/applications.h,, Header,+,applications/services/bt/bt_service/bt.h,, @@ -880,6 +880,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* @@ -3734,8 +3736,8 @@ 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_jarolift_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint8_t, uint16_t, 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_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_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*" From 09fc86415a15e8f24b4b5820fdf6552526eb74d9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 4 May 2026 22:59:24 +0300 Subject: [PATCH 180/202] force build params --- site_scons/commandline.scons | 4 ++-- site_scons/firmwareopts.scons | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/site_scons/commandline.scons b/site_scons/commandline.scons index c4c2fb5ab..21a02414a 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/site_scons/firmwareopts.scons b/site_scons/firmwareopts.scons index 6af861324..4ef210f89 100644 --- a/site_scons/firmwareopts.scons +++ b/site_scons/firmwareopts.scons @@ -28,7 +28,7 @@ else: "NDEBUG", ], CCFLAGS=[ - "-Og", + "-Os", ], ) From 91f4ef973edc50a71c54cdce63fb7b084b84bec4 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Tue, 5 May 2026 01:14:46 +0300 Subject: [PATCH 181/202] upd changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d26747d8..3f136db43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ * NFC: Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL) * OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) * OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**29apr2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**5may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * UI: Add `canvas_get_buffer`, `canvas_get_buffer_size` to public API * HID App: PTT improvements (PR #996 | by @hryamzik) From 76c067f66a145b56b88a146a3276cdeb535b7666 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 6 May 2026 13:48:49 +0300 Subject: [PATCH 182/202] upd changelog --- CHANGELOG.md | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f136db43..16e83b7e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,29 +1,8 @@ ## Main changes - Current API: 87.8 -* SubGHz: Add support for **42+ Keeloq based systems** (with partial Add Manually support) (see [Full list](/documentation/SubGHzSupportedSystems.md)) (by @zero-mega, @xMasterX, ARF Team) -* SubGHz: Add **Allstar Firefly 318ALD31K** protocol (18 bits, Static) (PR #989 | by @jlaughter) -* SubGHz: Add **Nord ICE** protocol (33 bits, Static) -* SubGHz: **Better support for CAME Atomo** type remotes (TOPD4REN) (decode + button codes) (thx to Roman for raw recordings) -* SubGHz: Add **CAME TOP44FGN** support in CAME TWEE protocol -* SubGHz: Add all 0x0s and all 0xFs KeeLoq MF codes for normal and simple learning -* SubGHz: **Fix CAME TWEE repeats count for button click** -* SubGHz: Improve Nice FLO decoding (thx to Roman for raw recordings) -* NFC: Add **ISO15693-3 and SLIX write-back support** (PR #984 | by @DoniyorI) -* NFC: **Fix "MIR" and other EMV cards crash on Read** (by @Dmitry422) -* NFC: Add **Mifare Ultralight C Write Support** (by @haw8411) -* NFC: Add **new parsers SZPPK, SKPPK and SevPPK**, upgrade Plantain parser, fix TwoCities parser (PR #981 | by @mxcdoam) -* NFC: Fix CLI with NTAG4xx and Type 4 Tag support (by @WillyJL) -* OFW PR 4362: NFC: **Fix BusFault** in Write to Initial Card (by @akrylysov) -* OFW PR 4369: NFC: Fix stack buffer overflows in MFUL FAST_READ and DESFire file settings parsers (by @qp-x-qp) -* Apps: Build tag (**5may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**6may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* UI: Add `canvas_get_buffer`, `canvas_get_buffer_size` to public API -* HID App: PTT improvements (PR #996 | by @hryamzik) -* UI: Wording changes to be more clear (PR #994 | by @SkeletonMan03) -* SubGHz: Fix duplicate code in RAW protocol -* SubGHz: Fix small logic error in KingGatesStylo4k protocol (found by @X-Stuff) -* OFW PR 4364: JS: Address utf8 support when uploading JavaScript application (by @bekindpleaserewind) -* Display: Extend lcd contrast range to full ST756x 6-bit range (by @ShaTie) (reduced to -10 to +18 to avoid fully unreadable state) +* Apps: Fixed crash in SubGHz Wardriving when changing GPS baudrate with GPS module connected

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 466c923dd14a586727143c0c82470e35da80210a Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Fri, 8 May 2026 19:05:42 +0300 Subject: [PATCH 183/202] upd changelog --- CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16e83b7e4..53e1f711f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ ## Main changes - Current API: 87.8 -* Apps: Build tag (**6may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* This release is the hotfix and upgrade for 2 apps below, other changes are same as in version 087 +* Apps: Moved **Nearby Files** to Base pack and **added option to set baudrate** and **add/update GPS position to extisting files** (SubGHz, NFC, RFID, iButton) +* Apps: **Fixed crash in SubGHz Wardriving** when changing GPS baudrate with GPS module connected +* Apps: Build tag (**8may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* Apps: Fixed crash in SubGHz Wardriving when changing GPS baudrate with GPS module connected +* Apps: Fixed Lat/Lon placement in SubGHz Wardriving when file is saved (by @WillyJL)

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 41628a4ce9df1289c6d34a6b7a969cee9109c032 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 9 May 2026 15:58:53 +0300 Subject: [PATCH 184/202] hotfix faac slh --- lib/subghz/protocols/faac_slh.c | 77 ++++++++++++++++----------------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index fb82b1379..3e853ec02 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -123,6 +123,41 @@ void subghz_protocol_encoder_faac_slh_free(void* context) { free(instance); } +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; + 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 @@ -235,16 +270,6 @@ static bool subghz_protocol_faac_slh_gen_data(SubGhzProtocolEncoderFaacSLH* inst } } } - 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(allow_zero_seed || (instance->generic.seed != 0x0)) { // check OFEX mode @@ -275,32 +300,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( @@ -322,7 +322,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); @@ -618,8 +618,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); From 44ff715a3e2247db8864c76fc6c256be0571d355 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 9 May 2026 16:00:55 +0300 Subject: [PATCH 185/202] upd changelog --- CHANGELOG.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53e1f711f..73edec75d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,10 @@ ## Main changes - Current API: 87.8 -* This release is the hotfix and upgrade for 2 apps below, other changes are same as in version 087 -* Apps: Moved **Nearby Files** to Base pack and **added option to set baudrate** and **add/update GPS position to extisting files** (SubGHz, NFC, RFID, iButton) -* Apps: **Fixed crash in SubGHz Wardriving** when changing GPS baudrate with GPS module connected +* This release is the hotfix for the SubGHz protocol below, other changes are same as in version 088/087 +* SubGHz: **Fix FAAC SLH wrong decode/encode**, apply little code cleanup * Apps: Build tag (**8may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes -* Apps: Fixed Lat/Lon placement in SubGHz Wardriving when file is saved (by @WillyJL) +* None

#### Known NFC post-refactor regressions list: - Mifare Mini clones reading is broken (original mini working fine) (OFW) From 327e5015929ffb97ded1f87f498a51dadb39b649 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 12 May 2026 22:39:47 +0200 Subject: [PATCH 186/202] Fix for uFBT too --- scripts/ufbt/project_template/.clangd | 4 ++++ 1 file changed, 4 insertions(+) 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: From c5bcab305032cc0848b7223f972e193ebd3a78e8 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 24 May 2026 02:12:59 +0300 Subject: [PATCH 187/202] upd changelog --- CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73edec75d..14f7949e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,6 @@ ## Main changes - Current API: 87.8 -* This release is the hotfix for the SubGHz protocol below, other changes are same as in version 088/087 -* SubGHz: **Fix FAAC SLH wrong decode/encode**, apply little code cleanup -* Apps: Build tag (**8may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**24may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * None

From 78cbcab365895e9efc5304253d7feb7e472500ec Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 31 May 2026 00:56:45 +0300 Subject: [PATCH 188/202] bump apps pack --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14f7949e7..5673ef3ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## Main changes - Current API: 87.8 -* Apps: Build tag (**24may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) +* Apps: Build tag (**30may2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes * None

From 27015e81eac73323cc537ea41f78c2db41f80ca9 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 15:12:38 +0200 Subject: [PATCH 189/202] Sub-GHz: Move Add Manually menu out of main app, transparently --- applications/main/subghz/application.fam | 37 +++++++++ .../main/subghz/helpers/subghz_txrx.c | 4 + .../main/subghz/scenes/subghz_scene_config.h | 24 +++--- .../subghz/scenes/subghz_scene_save_name.c | 53 ++++++++----- .../subghz/scenes/subghz_scene_save_success.c | 7 ++ .../subghz/scenes/subghz_scene_set_counter.c | 2 - .../main/subghz/scenes/subghz_scene_set_key.c | 2 - .../subghz/scenes/subghz_scene_set_seed.c | 2 - .../subghz/scenes/subghz_scene_set_type.c | 10 +-- .../main/subghz/scenes/subghz_scene_start.c | 28 +++++-- applications/main/subghz/subghz.c | 77 ++++++++++++++++++- 11 files changed, 195 insertions(+), 51 deletions(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index e143217b4..1b62541db 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -16,6 +16,14 @@ App( "!subghz_cli.c", "!helpers/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", @@ -36,6 +44,35 @@ App( # fap_category="Sub-GHz", # ) +App( + appid="subghz_add_manually", + name="Sub-GHz Add Manually", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="subghz_add_manually", + stack_size=3 * 1024, + 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( appid="subghz_gps", targets=["f7"], diff --git a/applications/main/subghz/helpers/subghz_txrx.c b/applications/main/subghz/helpers/subghz_txrx.c index 1da282793..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); 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_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_set_counter.c b/applications/main/subghz/scenes/subghz_scene_set_counter.c index be13499b8..130f9b048 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_counter.c +++ b/applications/main/subghz/scenes/subghz_scene_set_counter.c @@ -296,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 b3bd447e6..0d844d0dc 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_seed.c +++ b/applications/main/subghz/scenes/subghz_scene_set_seed.c @@ -119,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_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 7ee72ea21..6b44d39e4 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -104,9 +104,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); } @@ -275,8 +272,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."); @@ -293,16 +288,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) diff --git a/applications/main/subghz/scenes/subghz_scene_start.c b/applications/main/subghz/scenes/subghz_scene_start.c index 8c77eacd2..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 @@ -75,11 +76,28 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) { } else if(event.event == SubmenuIndexSaved) { scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSaved); return true; - } else if(event.event == SubmenuIndexAddManually) { - scene_manager_next_scene(subghz->scene_manager, SubGhzSceneSetType); - return true; - } else if(event.event == 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_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer); diff --git a/applications/main/subghz/subghz.c b/applications/main/subghz/subghz.c index e6d32013e..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" @@ -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( @@ -238,8 +248,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) { 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; @@ -263,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); @@ -270,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; } @@ -280,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); @@ -290,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); @@ -311,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); @@ -345,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); @@ -352,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); @@ -368,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; @@ -432,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 { @@ -459,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 From 35fec7c2108551630a3211eeb93c03d1c5251629 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 15:14:03 +0200 Subject: [PATCH 190/202] Fix comment --- applications/main/subghz/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 1b62541db..20fc62ad5 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -7,7 +7,7 @@ App( icon="A_Sub1ghz_14", stack_size=3 * 1024, order=10, - # Sources separation breaks linking with subghz on internal, commented for now + # Sources separation breaks linking with subghz on internal sources=[ "*.c", "!subghz_fap.c", From 4bdd7a391f2cc4e3467ec330ce21e9f64393a639 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 15:14:47 +0200 Subject: [PATCH 191/202] Fix source excludes Can't use ! with subdirs, only with bare filenames --- applications/main/subghz/application.fam | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 20fc62ad5..642651d22 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -11,10 +11,10 @@ App( sources=[ "*.c", "!subghz_fap.c", - "!helpers/subghz_gps.c", - "!helpers/minmea.c", + "!subghz_gps.c", + "!minmea.c", "!subghz_cli.c", - "!helpers/subghz_chat.c", + "!subghz_chat.c", "!subghz_extended_freq.c", "!subghz_gen_info.c", "!subghz_txrx_create_protocol_key.c", From 7ae97166226fac49b2e5edd9696e9cdc9073f052 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 15:15:37 +0200 Subject: [PATCH 192/202] Sub-GHz: More minimum free heap, prevents some crashes --- applications/main/subghz/subghz_history.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/subghz_history.c b/applications/main/subghz/subghz_history.c index be61d3526..39896ce88 100644 --- a/applications/main/subghz/subghz_history.c +++ b/applications/main/subghz/subghz_history.c @@ -6,7 +6,7 @@ #define SUBGHZ_HISTORY_MAX 65535 // uint16_t index max, ram limit below // #define SUBGHZ_HISTORY_FREE_HEAP (23624 * (1 - MIN(rpc_get_sessions_count(instance->rpc), 1U))) -#define SUBGHZ_HISTORY_FREE_HEAP (2048) +#define SUBGHZ_HISTORY_FREE_HEAP (4 * 1024) #define TAG "SubGhzHistory" From c298ed01ac81a7deb8b9d556496128bae4acc551 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 15:17:05 +0200 Subject: [PATCH 193/202] Fix startup hook sources --- applications/main/subghz/application.fam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/main/subghz/application.fam b/applications/main/subghz/application.fam index 642651d22..d80978b8e 100644 --- a/applications/main/subghz/application.fam +++ b/applications/main/subghz/application.fam @@ -96,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, ) From cdf5e2fb74e47fb1c41f2475c606896fc381a845 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 18:26:37 +0200 Subject: [PATCH 194/202] Update changelog --- CHANGELOG.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca40c1f0d..cd330fb8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,3 @@ -### Known Issues: -- Sub-GHz app is now external due to running out of space in flash, but it uses almost all available RAM. This means that: - - qFlipper / Mobile App cannot be used together with Sub-GHz app for now - - Only 20-30 signals can be kept at a time in receive history while in Read mode - - Opening Sub-GHz after using a few other apps may result in an out of memory warning, simply reboot and it will work again - ### Added: - Apps: - Games: Checkers (by @H4W9) @@ -65,6 +59,12 @@ - Unitemp: Numerous improvements from @MLAB-project fork (by @MLAB-project) - XRemote: Add dolphin xp and crash bugfix (by @teohumeau) - 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) From 155e91e00f4f354ab58cd0db65b083dedb6dc546 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 18:35:24 +0200 Subject: [PATCH 195/202] Fix changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd330fb8c..254b12d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -93,7 +93,9 @@ - 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) +- 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: USB: Fix USB HID keyboard LED state reporting (by @Caballosanex) From e8c4530f0210d2eb9fa891c50302ba3dfa1b5826 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Mon, 1 Jun 2026 21:50:50 +0200 Subject: [PATCH 196/202] Update changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 254b12d86..a42f80e27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,16 @@ - 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 (R-Tech) 12bit discr. fix & button 9 support (two buttons hold simulation) (mapped on arrow keys) (by @xMasterX) @@ -27,16 +31,23 @@ - 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) -- NFC: Add Mifare Ultralight C Write Support (#524 by @haw8411) -- OFW: RFID: Add Indala 224-bit (long format) protocol support (by @kuzaxak) + - 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) - CAN Tools: Parity with DBC format, support importing DBC files (by @MatthewKuKanich) - ESP Flasher: Bump Marauder 1.10.2 (by @justcallmekoko) - ESP32 WiFi Marauder: Marauder 1.10.0 support (by @justcallmekoko), Marauder 1.9.0 support (by @H4W9) @@ -76,8 +87,9 @@ - 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) -- OFW: RFID: Make FDX-B readout more descriptive (by @snowsign) -- OFW: API: Make `view_port_send_to_back()` public (by @loftyinclination) +- 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: @@ -90,18 +102,27 @@ - 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: USB: Fix USB HID keyboard LED state reporting (by @Caballosanex) +- 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 From 44d0e060c7bc025d728c1df4891857f6ce6e4980 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 2 Jun 2026 22:03:21 +0200 Subject: [PATCH 197/202] Sub-GHz: Revert protocol diffs from old OFW PR, sync to Unleashed --- lib/subghz/protocols/feron.c | 2 - lib/subghz/protocols/gangqi.c | 69 ++++++++++++++---------------- lib/subghz/protocols/hollarm.c | 2 - lib/subghz/protocols/keeloq.c | 2 +- lib/subghz/protocols/legrand.c | 64 ++++++++++------------------ lib/subghz/protocols/marantec24.c | 70 ++++++++++++++----------------- lib/subghz/protocols/revers_rb2.c | 39 ++++++++--------- lib/subghz/protocols/roger.c | 2 - 8 files changed, 103 insertions(+), 147 deletions(-) diff --git a/lib/subghz/protocols/feron.c b/lib/subghz/protocols/feron.c index 3bcb29f97..76a0e72a5 100644 --- a/lib/subghz/protocols/feron.c +++ b/lib/subghz/protocols/feron.c @@ -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) { diff --git a/lib/subghz/protocols/gangqi.c b/lib/subghz/protocols/gangqi.c index 86552e0fe..cdef6e7f6 100644 --- a/lib/subghz/protocols/gangqi.c +++ b/lib/subghz/protocols/gangqi.c @@ -80,7 +80,7 @@ void* subghz_protocol_encoder_gangqi_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 1024; + instance->encoder.size_upload = 256; instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); instance->encoder.is_running = false; return instance; @@ -187,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); } } } @@ -271,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"); @@ -295,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) { @@ -343,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; @@ -377,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) < diff --git a/lib/subghz/protocols/hollarm.c b/lib/subghz/protocols/hollarm.c index c8af8c7f5..0a5a6ab35 100644 --- a/lib/subghz/protocols/hollarm.c +++ b/lib/subghz/protocols/hollarm.c @@ -267,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"); @@ -291,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) { diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 2b0fefb62..5212eacb3 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1042,7 +1042,7 @@ static uint32_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; diff --git a/lib/subghz/protocols/legrand.c b/lib/subghz/protocols/legrand.c index 771e28023..856cad9dc 100644 --- a/lib/subghz/protocols/legrand.c +++ b/lib/subghz/protocols/legrand.c @@ -85,8 +85,7 @@ void* subghz_protocol_encoder_legrand_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = - (subghz_protocol_legrand_const.min_count_bit_for_found * 6) * 2 + 2; + 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; } @@ -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/marantec24.c b/lib/subghz/protocols/marantec24.c index a012d0b52..94120ad3b 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -76,7 +76,7 @@ void* subghz_protocol_encoder_marantec24_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 512; + 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) { @@ -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) < diff --git a/lib/subghz/protocols/revers_rb2.c b/lib/subghz/protocols/revers_rb2.c index 06dc7b0ca..84a8be706 100644 --- a/lib/subghz/protocols/revers_rb2.c +++ b/lib/subghz/protocols/revers_rb2.c @@ -80,7 +80,7 @@ void* subghz_protocol_encoder_revers_rb2_alloc(SubGhzEnvironment* environment) { instance->generic.protocol_name = instance->base.protocol->name; instance->encoder.repeat = 3; - instance->encoder.size_upload = 1768; + 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) { diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index 85f31c66d..35d80788d 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -275,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++) { @@ -295,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) { From 4ae0c47588802335d22431c150776222af3318f8 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Tue, 2 Jun 2026 22:05:47 +0200 Subject: [PATCH 198/202] Format --- applications/system/hid_app/assets/for_11x5.png | Bin 335 -> 80 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/applications/system/hid_app/assets/for_11x5.png b/applications/system/hid_app/assets/for_11x5.png index 3ebc4f23c723185c368b7be348c653ef75427955..42495d203deab10c3837824ba12c4f0888d09f49 100644 GIT binary patch delta 50 zcmX@l6fi+WN8HoJF{ENn@}Bc2ik@p+UeE5s$>Vccf9;OXk;vd$@? F2>|5K5%d56 literal 335 zcmeAS@N?(olHy`uVBq!ia0vp^+(69A2qYMG-_`yNq!f}pf_xbms?-=58d?|_egTCV zUNA6}8Za=tN?>5Hn!&&zUNC1@pbb!hDaqU2g@N&Im+%rGkG;gx*OmPhi;Re>^xMDZ zSb#zT$%%etKw27zS%DY~yz;AO0U6?+E{-7_*OPbrZ%8a>K75UZ?IKItT}}prjjRjA zbDC!Zm8h1uMwFx^mZVxG7o`Fz1|tI_GhG7{T_cMSBLgdA6Dwm2Z36=<1A{-hrCul+ za`RI%(<(t4Omz(nbPX&+jEt-djjasKAsQZ3nC}H@(16=el9`)YT#}eufNqJol@U}A T_XhP?pdJQKS3j3^P6 Date: Tue, 2 Jun 2026 22:12:40 +0200 Subject: [PATCH 199/202] JS: Bump SDK for Infrared module from months ago (oops) --nobuild --- applications/system/js_app/packages/fz-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From dafdae9a51ccbd73b28be8329a98d512c9665739 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Fri, 2 Jan 2026 23:44:13 +0100 Subject: [PATCH 200/202] NFC: Fix typo in SLIX poller --- lib/nfc/protocols/slix/slix_poller.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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, From f91daf3e132be1c1d5b97973a2b3bf5bb80d43ed Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 3 Jun 2026 01:12:55 +0200 Subject: [PATCH 201/202] Apps: Update apps --- CHANGELOG.md | 23 ++++++++++++++++------- applications/external | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a42f80e27..12ed917e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,27 +48,36 @@ UL: API: Add `canvas_get_buffer`, `canvas_get_buffer_size` to public API (by @xM - 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.10.2 (by @justcallmekoko) - - ESP32 WiFi Marauder: Marauder 1.10.0 support (by @justcallmekoko), Marauder 1.9.0 support (by @H4W9) + - 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 (by @H4W9) - - Free Roam: Minimap, code optimization, ux improvements (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) - 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) - - Picopass: Option to Create credential without a card (by @redteamlife), info screen improvements, allow Emulation of NRMAC dumped cards (by @bettse) - - Seader: SAM ATR3 support, better IFSC/IFSD handling, various improvements (by @bettse), reverse engineered U90 packet, memory optimization (by @cindersocket) + - 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) - Tetris: Various bugfixes (by @Bricktech2000) - - Unitemp: Numerous improvements from @MLAB-project fork (by @MLAB-project) + - 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 diff --git a/applications/external b/applications/external index 805c3e7fd..d96dfcb43 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit 805c3e7fda279ac41e06b1c82e127db0f99c32b6 +Subproject commit d96dfcb43152cfe9b8afd61526feb027db3e921c From 8ed809fba8af7ac3f09b9495a597d8963f9178a8 Mon Sep 17 00:00:00 2001 From: WillyJL Date: Wed, 3 Jun 2026 01:16:46 +0200 Subject: [PATCH 202/202] Fix broken submodule --- applications/external | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/external b/applications/external index d96dfcb43..b05485f7d 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit d96dfcb43152cfe9b8afd61526feb027db3e921c +Subproject commit b05485f7d13ee1595d06745c881f1d3aadb3d45d