From 8f203f47d9e1eea937bcade5eb126ee1fa31ae2c Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:48:30 +0300 Subject: [PATCH 01/27] comunello add manually support --- .../main/subghz/helpers/subghz_custom_event.h | 2 ++ .../subghz/scenes/subghz_scene_set_type.c | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index bd8dee161..bdd5f849d 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -79,6 +79,8 @@ typedef enum { SetTypeDoorHan_433_92, SetTypeBeninca433, SetTypeBeninca868, + SetTypeComunello433, + SetTypeComunello868, SetTypeAllmatic433, SetTypeAllmatic868, SetTypeCenturion433, diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 3155f9f1e..f70aec803 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -25,6 +25,8 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", [SetTypeBeninca433] = "KL: Beninca 433MHz", [SetTypeBeninca868] = "KL: Beninca 868MHz", + [SetTypeComunello433] = "KL: Comunello 433MHz", + [SetTypeComunello868] = "KL: Comunello 868MHz", [SetTypeAllmatic433] = "KL: Allmatic 433MHz", [SetTypeAllmatic868] = "KL: Allmatic 868MHz", [SetTypeCenturion433] = "KL: Centurion 433MHz", @@ -400,6 +402,26 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .keeloq.cnt = 0x05, .keeloq.manuf = "Beninca"}; break; + case SetTypeComunello433: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = key & 0x00FFFFFF, + .keeloq.btn = 0x08, + .keeloq.cnt = 0x05, + .keeloq.manuf = "Comunello"}; + break; + case SetTypeComunello868: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 868460000, + .keeloq.serial = key & 0x00FFFFFF, + .keeloq.btn = 0x08, + .keeloq.cnt = 0x05, + .keeloq.manuf = "Comunello"}; + break; case SetTypeAllmatic433: gen_info = (GenInfo){ .type = GenKeeloq, From aad07ed9431b4397073d16231b008a96ba1f0e07 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:57:30 +0300 Subject: [PATCH 02/27] expansion and serial fixes and new api by HaxSam & WillyJL --- CHANGELOG.md | 2 + applications/services/expansion/expansion.c | 66 ++++++++++++++++-- applications/services/expansion/expansion.h | 9 +++ .../services/expansion/expansion_worker.c | 8 ++- .../services/expansion/expansion_worker.h | 8 ++- targets/f7/furi_hal/furi_hal_serial.c | 67 +++++++++++-------- 6 files changed, 122 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4deadc0..3e3969f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) +* Expansion: add is_connected api (by @HaxSam & @WillyJL) * RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" * GUI: Added `submenu_remove_item()` to API, was needed for NFC Type 4 related changes (by @WillyJL) * SubGHz: Fix possible frequency analyzer deadlock when holding Ok (by @WillyJL) diff --git a/applications/services/expansion/expansion.c b/applications/services/expansion/expansion.c index 219bf0641..2b0c5b27a 100644 --- a/applications/services/expansion/expansion.c +++ b/applications/services/expansion/expansion.c @@ -18,6 +18,7 @@ typedef enum { ExpansionStateDisabled, ExpansionStateEnabled, ExpansionStateRunning, + ExpansionStateConnectionEstablished, } ExpansionState; typedef enum { @@ -27,10 +28,13 @@ typedef enum { ExpansionMessageTypeReloadSettings, ExpansionMessageTypeModuleConnected, ExpansionMessageTypeModuleDisconnected, + ExpansionMessageTypeConnectionEstablished, + ExpansionMessageTypeIsConnected, } ExpansionMessageType; typedef union { FuriHalSerialId serial_id; + bool* is_connected; } ExpansionMessageData; typedef struct { @@ -67,13 +71,21 @@ static void expansion_detect_callback(void* context) { UNUSED(status); } -static void expansion_worker_callback(void* context) { +static void expansion_worker_callback(void* context, ExpansionWorkerCallbackReason reason) { furi_assert(context); Expansion* instance = context; - ExpansionMessage message = { - .type = ExpansionMessageTypeModuleDisconnected, - .api_lock = NULL, // Not locking the API here to avoid a deadlock + ExpansionMessage message; + switch(reason) { + case ExpansionWorkerCallbackReasonExit: + message.type = ExpansionMessageTypeModuleDisconnected; + message.api_lock = NULL; // Not locking the API here to avoid a deadlock + break; + + case ExpansionWorkerCallbackReasonConnected: + message.type = ExpansionMessageTypeConnectionEstablished; + message.api_lock = api_lock_alloc_locked(); + break; }; const FuriStatus status = furi_message_queue_put(instance->queue, &message, FuriWaitForever); @@ -106,7 +118,9 @@ static void UNUSED(data); if(instance->state == ExpansionStateDisabled) { return; - } else if(instance->state == ExpansionStateRunning) { + } else if( + instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); } else { @@ -124,7 +138,9 @@ static void expansion_control_handler_set_listen_serial( if(instance->state != ExpansionStateDisabled && instance->serial_id == data->serial_id) { return; - } else if(instance->state == ExpansionStateRunning) { + } else if( + instance->state == ExpansionStateRunning || + instance->state == ExpansionStateConnectionEstablished) { expansion_worker_stop(instance->worker); expansion_worker_free(instance->worker); @@ -182,7 +198,8 @@ static void expansion_control_handler_module_disconnected( Expansion* instance, const ExpansionMessageData* data) { UNUSED(data); - if(instance->state != ExpansionStateRunning) { + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { return; } @@ -192,6 +209,23 @@ static void expansion_control_handler_module_disconnected( instance->serial_id, expansion_detect_callback, instance); } +static void expansion_control_handler_connection_established( + Expansion* instance, + const ExpansionMessageData* data) { + UNUSED(data); + if(instance->state != ExpansionStateRunning && + instance->state != ExpansionStateConnectionEstablished) { + return; + } + + instance->state = ExpansionStateConnectionEstablished; +} + +static void + expansion_control_handler_is_connected(Expansion* instance, const ExpansionMessageData* data) { + *data->is_connected = instance->state == ExpansionStateConnectionEstablished; +} + typedef void (*ExpansionControlHandler)(Expansion*, const ExpansionMessageData*); static const ExpansionControlHandler expansion_control_handlers[] = { @@ -201,6 +235,8 @@ static const ExpansionControlHandler expansion_control_handlers[] = { [ExpansionMessageTypeReloadSettings] = expansion_control_handler_reload_settings, [ExpansionMessageTypeModuleConnected] = expansion_control_handler_module_connected, [ExpansionMessageTypeModuleDisconnected] = expansion_control_handler_module_disconnected, + [ExpansionMessageTypeConnectionEstablished] = expansion_control_handler_connection_established, + [ExpansionMessageTypeIsConnected] = expansion_control_handler_is_connected, }; static int32_t expansion_control(void* context) { @@ -295,6 +331,22 @@ void expansion_disable(Expansion* instance) { api_lock_wait_unlock_and_free(message.api_lock); } +bool expansion_is_connected(Expansion* instance) { + furi_check(instance); + bool is_connected; + + ExpansionMessage message = { + .type = ExpansionMessageTypeIsConnected, + .data.is_connected = &is_connected, + .api_lock = api_lock_alloc_locked(), + }; + + furi_message_queue_put(instance->queue, &message, FuriWaitForever); + api_lock_wait_unlock_and_free(message.api_lock); + + return is_connected; +} + void expansion_set_listen_serial(Expansion* instance, FuriHalSerialId serial_id) { furi_check(instance); furi_check(serial_id < FuriHalSerialIdMax); diff --git a/applications/services/expansion/expansion.h b/applications/services/expansion/expansion.h index e169b3c15..1b0879b1e 100644 --- a/applications/services/expansion/expansion.h +++ b/applications/services/expansion/expansion.h @@ -50,6 +50,15 @@ void expansion_enable(Expansion* instance); */ void expansion_disable(Expansion* instance); +/** + * @brief Check if an expansion module is connected. + * + * @param[in,out] instance pointer to the Expansion instance. + * + * @returns true if the module is connected and initialized, false otherwise. + */ +bool expansion_is_connected(Expansion* instance); + /** * @brief Enable support for expansion modules on designated serial port. * diff --git a/applications/services/expansion/expansion_worker.c b/applications/services/expansion/expansion_worker.c index ac2a5935b..e6d17c152 100644 --- a/applications/services/expansion/expansion_worker.c +++ b/applications/services/expansion/expansion_worker.c @@ -35,7 +35,8 @@ typedef enum { ExpansionWorkerFlagError = 1 << 2, } ExpansionWorkerFlag; -#define EXPANSION_ALL_FLAGS (ExpansionWorkerFlagData | ExpansionWorkerFlagStop) +#define EXPANSION_ALL_FLAGS \ + (ExpansionWorkerFlagData | ExpansionWorkerFlagStop | ExpansionWorkerFlagError) struct ExpansionWorker { FuriThread* thread; @@ -225,6 +226,7 @@ static bool expansion_worker_handle_state_handshake( if(furi_hal_serial_is_baud_rate_supported(instance->serial_handle, baud_rate)) { instance->state = ExpansionWorkerStateConnected; + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonConnected); // Send response at previous baud rate if(!expansion_worker_send_status_response(instance, ExpansionFrameErrorNone)) break; furi_hal_serial_set_br(instance->serial_handle, baud_rate); @@ -360,6 +362,8 @@ static int32_t expansion_worker(void* context) { expansion_worker_state_machine(instance); } + furi_hal_serial_async_rx_stop(instance->serial_handle); + if(instance->state == ExpansionWorkerStateRpcActive) { expansion_worker_rpc_session_close(instance); } @@ -371,7 +375,7 @@ static int32_t expansion_worker(void* context) { // Do not invoke worker callback on user-requested exit if((instance->exit_reason != ExpansionWorkerExitReasonUser) && (instance->callback != NULL)) { - instance->callback(instance->cb_context); + instance->callback(instance->cb_context, ExpansionWorkerCallbackReasonExit); } return 0; diff --git a/applications/services/expansion/expansion_worker.h b/applications/services/expansion/expansion_worker.h index 761f79c1d..faab2887f 100644 --- a/applications/services/expansion/expansion_worker.h +++ b/applications/services/expansion/expansion_worker.h @@ -17,14 +17,20 @@ */ typedef struct ExpansionWorker ExpansionWorker; +typedef enum { + ExpansionWorkerCallbackReasonExit, + ExpansionWorkerCallbackReasonConnected, +} ExpansionWorkerCallbackReason; + /** * @brief Worker callback type. * * @see expansion_worker_set_callback() * * @param[in,out] context pointer to a user-defined object. + * @param[in] reason reason for the callback. */ -typedef void (*ExpansionWorkerCallback)(void* context); +typedef void (*ExpansionWorkerCallback)(void* context, ExpansionWorkerCallbackReason reason); /** * @brief Create an expansion worker instance. diff --git a/targets/f7/furi_hal/furi_hal_serial.c b/targets/f7/furi_hal/furi_hal_serial.c index 8ad9794a8..e5c30a278 100644 --- a/targets/f7/furi_hal/furi_hal_serial.c +++ b/targets/f7/furi_hal/furi_hal_serial.c @@ -817,6 +817,21 @@ static void furi_hal_serial_async_rx_configure( FuriHalSerialHandle* handle, FuriHalSerialAsyncRxCallback callback, void* context) { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { + LL_USART_DisableIT_RXNE_RXFNE(USART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); + furi_hal_serial_usart_deinit_dma_rx(); + } else if(handle->id == FuriHalSerialIdLpuart) { + LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); + furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); + furi_hal_serial_lpuart_deinit_dma_rx(); + } + } + // Handle must be configured before enabling RX interrupt // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = callback; @@ -824,27 +839,17 @@ static void furi_hal_serial_async_rx_configure( furi_hal_serial[handle->id].rx_dma_callback = NULL; furi_hal_serial[handle->id].context = context; - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { furi_hal_serial_usart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); LL_USART_EnableIT_RXNE_RXFNE(USART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); - furi_hal_serial_usart_deinit_dma_rx(); - LL_USART_DisableIT_RXNE_RXFNE(USART1); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { + } else if(handle->id == FuriHalSerialIdLpuart) { furi_hal_serial_lpuart_deinit_dma_rx(); furi_hal_interrupt_set_isr( FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); LL_LPUART_EnableIT_RXNE_RXFNE(LPUART1); - } else { - furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); - furi_hal_serial_lpuart_deinit_dma_rx(); - LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); } } } @@ -944,33 +949,39 @@ static void furi_hal_serial_dma_configure( FuriHalSerialHandle* handle, FuriHalSerialDmaRxCallback callback, void* context) { - furi_check(handle); - - if(handle->id == FuriHalSerialIdUsart) { - if(callback) { - furi_hal_serial_usart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); - } else { + // Disable RXFNE interrupts before unsetting the user callback that reads data + // Otherwise interrupt runs without reading data and without clearing RXFNE flag + // This would cause a system hang as the same interrupt runs in loop forever + if(!callback) { + if(handle->id == FuriHalSerialIdUsart) { LL_USART_DisableIT_RXNE_RXFNE(USART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdUart1, NULL, NULL); furi_hal_serial_usart_deinit_dma_rx(); - } - } else if(handle->id == FuriHalSerialIdLpuart) { - if(callback) { - furi_hal_serial_lpuart_init_dma_rx(); - furi_hal_interrupt_set_isr( - FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); - } else { + } else if(handle->id == FuriHalSerialIdLpuart) { LL_LPUART_DisableIT_RXNE_RXFNE(LPUART1); furi_hal_interrupt_set_isr(FuriHalInterruptIdLpUart1, NULL, NULL); furi_hal_serial_lpuart_deinit_dma_rx(); } } + + // Handle must be configured before enabling RX interrupt + // as it might be triggered right away on a misconfigured handle furi_hal_serial[handle->id].rx_byte_callback = NULL; furi_hal_serial[handle->id].handle = handle; furi_hal_serial[handle->id].rx_dma_callback = callback; furi_hal_serial[handle->id].context = context; + + if(callback) { + if(handle->id == FuriHalSerialIdUsart) { + furi_hal_serial_usart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdUart1, furi_hal_serial_usart_irq_callback, NULL); + } else if(handle->id == FuriHalSerialIdLpuart) { + furi_hal_serial_lpuart_init_dma_rx(); + furi_hal_interrupt_set_isr( + FuriHalInterruptIdLpUart1, furi_hal_serial_lpuart_irq_callback, NULL); + } + } } void furi_hal_serial_dma_rx_start( From e003a19edcfc3201c449f7be76c782af3adf9d55 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 17:59:27 +0300 Subject: [PATCH 03/27] add 868 46 mhz to default freq list --- CHANGELOG.md | 2 +- documentation/SubGHzSettings.md | 1 + lib/subghz/subghz_setting.c | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e3969f86..591829d8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ * SubGHz: V2 Phoenix show counter value * SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) * SubGHz: Fix CAME 24bit decoder -* SubGHz: Add 462.750 MHz to default subghz freqs list +* SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list * SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 95e19bdbf..89f3eba7f 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -68,6 +68,7 @@ if you need your custom one, make sure it doesn't listed here 779000000, 868350000, 868400000, + 868460000, 868800000, 868950000, 906400000, diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 8f41f576c..867459d05 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -74,6 +74,7 @@ static const uint32_t subghz_frequency_list[] = { 779000000, 868350000, 868400000, + 868460000, 868800000, 868950000, 906400000, From 95171046268ee0f2037e221a6cd1a63609fdb409 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 5 Jul 2025 19:14:48 +0300 Subject: [PATCH 04/27] Update doorhan programming instructions by @li0ard --- CHANGELOG.md | 1 + documentation/SubGHzRemoteProg.md | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 591829d8f..5116f3154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* Docs: Update doorhan programming instructions (by @li0ard) * FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) * Expansion: add is_connected api (by @HaxSam & @WillyJL) * RFID 125khz: Fix strange bug with LCD backlight going off after doing "Write" diff --git a/documentation/SubGHzRemoteProg.md b/documentation/SubGHzRemoteProg.md index c02c6ee43..18f726941 100644 --- a/documentation/SubGHzRemoteProg.md +++ b/documentation/SubGHzRemoteProg.md @@ -74,13 +74,20 @@ Watch this video to learn more and see how different boards can be programmed (v ## Doorhan With access to the receiver box: -1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) -2. Open your new remote file -3. Push `P` button for ~2 sec, led will start flashing -4. Press `Send` on your flipper for ~2 seconds -5. Led on the receiver board will flash and turn off -6. Done! +1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote or follow guide below) +- Finding frequency +There are 2 frequencies for DoorHan: 315.00 / 433.92. To determine them it is enough to create a DoorHan remote control with one of the frequencies via Sub-GHz -> Add manually, press the button and watch the receiver's reaction. If you have guessed the frequency, the light bulb will turn on when we press the button on the FZ and turn off when we release it. + +2. Binding the remote control + +Once you have access to the receiver (removed the protective cover), look at the buttons: +- If there are 4 buttons (Radio, Reverse, Auto, ...) then press and hold Radio until the LED lights up, then press the FZ button 2 times and the LED goes out; +- If there are 4 buttons (R, P, +, -) and display, press R, then press 2 times the button on FZ and wait +/- 10 seconds; +- If there are 4 buttons (+, -, F, TR) and display, press TR, then press 2 times the button on FZ and wait +/- 10 seconds; +- In other cases there is a “universal” instruction: Press and hold the button “P” +/- 2 seconds until the LED flashes, then press 2 times the button on the FZ and the LED goes out. + +In all cases it is recommended to wait until the receiver returns to normal mode. With existing remote: 1. Create new remote with randomly generated serial: Go to SubGHz -> Add Manually -> KL: Doorhan 433Mhz or 315Mhz depends on your receiver (find out by reading your existing remote) From 6ae1ce68610376ba5e198896d94588ccc9ca8df1 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 01:36:26 +0300 Subject: [PATCH 05/27] upd api symbols --- targets/f7/api_symbols.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 73ae7daae..d0cea83e2 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1071,6 +1071,7 @@ Function,-,exp2f,float,float Function,-,exp2l,long double,long double Function,+,expansion_disable,void,Expansion* Function,+,expansion_enable,void,Expansion* +Function,+,expansion_is_connected,_Bool,Expansion* Function,+,expansion_set_listen_serial,void,"Expansion*, FuriHalSerialId" Function,-,expf,float,float Function,-,expl,long double,long double From 43b35019ed168fd921313f51f1a84a2e939d0d32 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:42:16 +0300 Subject: [PATCH 06/27] subghz marantec protocol implement crc verification and add manually fix crc function, add valid/invalid display (does not affect TX) and use new crc sum in add manually menu --- .../main/subghz/helpers/subghz_custom_event.h | 2 ++ .../helpers/subghz_txrx_create_protocol_key.c | 25 ++++++++++++++ .../helpers/subghz_txrx_create_protocol_key.h | 7 ++++ .../subghz/scenes/subghz_scene_set_type.c | 27 +++++++++++++++ lib/subghz/protocols/marantec.c | 34 ++++++++++++++++--- lib/subghz/protocols/marantec.h | 8 +++++ 6 files changed, 99 insertions(+), 4 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index bdd5f849d..a3dae60b8 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -126,6 +126,8 @@ typedef enum { SetTypeHollarm_433, SetTypeReversRB2_433, SetTypeMarantec24_868, + SetTypeMarantec_433, + SetTypeMarantec_868, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW // SetTypeNeroRadio, //Deleted in OFW 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 63b892401..813b706b6 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -395,3 +396,27 @@ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { // serial | const_and_button *result_key = (serial << 18) | (const_and_button << 10) | (bytesum << 2); } + +void subghz_txrx_gen_key_marantec(uint64_t* result_key) { + uint64_t randkey = (uint64_t)rand(); + uint32_t serial = (uint32_t)((randkey) & 0xFFFFF); + // 0x130 is the constant + // 0x4 is the button code + // 0x86 is the serial constant + // serial is random value that we pre generate above + // At the end we will put the crc sum + uint64_t full_key_no_crc = (uint64_t)((uint64_t)0x130 << 40 | (uint64_t)serial << 20 | + (uint64_t)0x4 << 16 | (uint64_t)0x86 << 8); + + uint8_t tdata[6] = { + full_key_no_crc >> 48, + full_key_no_crc >> 40, + full_key_no_crc >> 32, + full_key_no_crc >> 24, + full_key_no_crc >> 16, + full_key_no_crc >> 8}; + + uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); + + *result_key = ((full_key_no_crc >> 8) << 8) | crc; +} 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 55932bd39..fba7acb6f 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -153,3 +153,10 @@ bool subghz_txrx_gen_secplus_v1_protocol( * @return uint64_t if success */ void subghz_txrx_gen_serial_gangqi(uint64_t* result_key); + +/** + * Generate key for Marantec protocol + * + * @param result_key Pointer to a uint64_t where the key will be stored + */ +void subghz_txrx_gen_key_marantec(uint64_t* result_key); diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index f70aec803..134d2e1d1 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -71,6 +71,8 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeHollarm_433] = "Hollarm 433MHz", [SetTypeReversRB2_433] = "Revers RB2 433MHz", [SetTypeMarantec24_868] = "Marantec24 868MHz", + [SetTypeMarantec_433] = "Marantec 433MHz", + [SetTypeMarantec_868] = "Marantec 868MHz", [SetTypeBETT_433] = "BETT 433MHz", [SetTypeLinear_300_00] = "Linear 300MHz", // [SetTypeNeroSketch] = "Nero Sketch", // Deleted in OFW @@ -192,6 +194,9 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { uint64_t gangqi_key; subghz_txrx_gen_serial_gangqi(&gangqi_key); + uint64_t marantec_key; + subghz_txrx_gen_key_marantec(&marantec_key); + GenInfo gen_info = {0}; switch(event.event) { case SetTypePricenton433: @@ -360,6 +365,28 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 24, .data.te = 0}; break; + case SetTypeMarantec_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = + SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end + .data.key = marantec_key, + .data.bits = 49, + .data.te = 0}; + break; + case SetTypeMarantec_868: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 868350000, + .data.name = + SUBGHZ_PROTOCOL_MARANTEC_NAME, // Button code is 0x4 and crc sum to the end + .data.key = marantec_key, + .data.bits = 49, + .data.te = 0}; + break; case SetTypeFaacSLH_433: gen_info = (GenInfo){ .type = GenFaacSLH, diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index fc4aa0dca..edb176635 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -165,7 +165,7 @@ static void subghz_protocol_encoder_marantec_get_upload(SubGhzProtocolEncoderMar } uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { - uint8_t crc = 0x08; + uint8_t crc = 0x01; size_t i, j; for(i = 0; i < len; i++) { crc ^= data[i]; @@ -184,6 +184,18 @@ uint8_t subghz_protocol_marantec_crc8(uint8_t* data, size_t len) { * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_marantec_remote_controller(SubGhzBlockGeneric* instance) { + // Key samples + // 1307EDF6486C5 = 000 100110000 01111110110111110110 0100 10000110 11000101 + // 1303EFAFD8683 = 000 100110000 00111110111110101111 1101 10000110 10000011 + + // From unittests + // 1300710DF869F + + // const serial button serial crc + // 130 7EDF6 4 86 C5 + // 130 3EFAF D 86 83 + // 130 0710D F 86 9F + instance->btn = (instance->data >> 16) & 0xF; instance->serial = ((instance->data >> 12) & 0xFFFFFF00) | ((instance->data >> 8) & 0xFF); } @@ -367,16 +379,30 @@ void subghz_protocol_decoder_marantec_get_string(void* context, FuriString* outp SubGhzProtocolDecoderMarantec* instance = context; subghz_protocol_marantec_remote_controller(&instance->generic); + uint8_t tdata[6] = { + instance->generic.data >> 48, + instance->generic.data >> 40, + instance->generic.data >> 32, + instance->generic.data >> 24, + instance->generic.data >> 16, + instance->generic.data >> 8}; + + uint8_t crc = subghz_protocol_marantec_crc8(tdata, sizeof(tdata)); + bool crc_ok = (crc == (instance->generic.data & 0xFF)); + furi_string_cat_printf( output, "%s %db\r\n" - "Key:0x%lX%08lX\r\n" - "Sn:0x%07lX \r\n" - "Btn:%X\r\n", + "Key: 0x%lX%08lX\r\n" + "Sn: 0x%07lX \r\n" + "CRC: 0x%02X - %s\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32), (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, + crc, + crc_ok ? "Valid" : "Invalid", instance->generic.btn); } diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 485c563b2..9a7b55b7e 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -107,3 +107,11 @@ 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); From e025c5742550b2a6609a3ee3a536c235e67c0835 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sun, 6 Jul 2025 18:47:12 +0300 Subject: [PATCH 07/27] Update marantec24 protocol info --- lib/subghz/protocols/marantec24.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/subghz/protocols/marantec24.c b/lib/subghz/protocols/marantec24.c index 588aa1e5a..ac602fc96 100644 --- a/lib/subghz/protocols/marantec24.c +++ b/lib/subghz/protocols/marantec24.c @@ -217,6 +217,11 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile // Marantec24 Decoder // 2024 - @xMasterX (MMX) + // 2025 update - The protocol is not real marantec, + // it comes from chinese remote that pretends to be replica of original marantec, actually it was a cloner + // which had some thing written on it, which is uknown, but since its pretentding to be marantec, + // it was decided to keep the name of the protocol as marantec24 (24 bits) + // Key samples // 101011000000010111001000 = AC05C8 // 101011000000010111000100 = AC05C4 @@ -266,16 +271,12 @@ void subghz_protocol_decoder_marantec24_feed(void* context, bool level, volatile //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) < - subghz_protocol_marantec24_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 4)) { + subghz_protocol_marantec24_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 0); } if((DURATION_DIFF( instance->decoder.te_last, subghz_protocol_marantec24_const.te_short) < - subghz_protocol_marantec24_const.te_delta) && - (DURATION_DIFF(duration, subghz_protocol_marantec24_const.te_long * 9) < - subghz_protocol_marantec24_const.te_delta * 4)) { + subghz_protocol_marantec24_const.te_delta)) { subghz_protocol_blocks_add_bit(&instance->decoder, 1); } // If got 24 bits key reading is finished From a547c946abbc3fcfae95c4aa2fe28d2d59d461a9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Jul 2025 04:16:58 +0300 Subject: [PATCH 08/27] reduce less popular freqs in default hopper preset, make it faster also add 303 mhz freq to default list --- documentation/SubGHzSettings.md | 5 ++--- lib/subghz/subghz_setting.c | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/documentation/SubGHzSettings.md b/documentation/SubGHzSettings.md index 89f3eba7f..682111bcd 100644 --- a/documentation/SubGHzSettings.md +++ b/documentation/SubGHzSettings.md @@ -12,6 +12,7 @@ if you need your custom one, make sure it doesn't listed here /* 300 - 348 */ 300000000, 302757000, + 303000000, 303875000, 303900000, 304250000, @@ -100,10 +101,8 @@ Your frequencies will be added after default ones ### Default hopper list ``` - 310000000, 315000000, - 318000000, - 418000000, 433920000, + 434420000, 868350000, ``` diff --git a/lib/subghz/subghz_setting.c b/lib/subghz/subghz_setting.c index 867459d05..49a0af58d 100644 --- a/lib/subghz/subghz_setting.c +++ b/lib/subghz/subghz_setting.c @@ -18,6 +18,7 @@ static const uint32_t subghz_frequency_list[] = { /* 300 - 348 */ 300000000, 302757000, + 303000000, 303875000, 303900000, 304250000, @@ -85,11 +86,9 @@ static const uint32_t subghz_frequency_list[] = { }; static const uint32_t subghz_hopper_frequency_list[] = { - 310000000, 315000000, - 318000000, - 418000000, 433920000, + 434420000, 868350000, 0, }; From 74f6ee1e7ceac5d2405cb00c64928d4c55296c03 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Mon, 7 Jul 2025 17:55:14 +0300 Subject: [PATCH 09/27] badusb fix modifier keys with HOLD/RELEASE commands by WillyJL --- CHANGELOG.md | 1 + applications/main/bad_usb/helpers/ducky_script.c | 13 ++++++++++--- .../main/bad_usb/helpers/ducky_script_commands.c | 4 ++-- applications/main/bad_usb/helpers/ducky_script_i.h | 4 +++- .../main/bad_usb/helpers/ducky_script_keycodes.c | 12 ++++++++++++ 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5116f3154..beab8f799 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ - SmartRider parser (by @jaylikesbunda) * Apps: **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev) ## Other changes +* BadUSB: Fix modifier keys with HOLD/RELEASE commands (by @WillyJL) * Docs: Update doorhan programming instructions (by @li0ard) * FuriHalSerial: Fix RXFNE interrupt hang, aka freezing with UART output when Expansion Modules are enabled (by @WillyJL) * Expansion: add is_connected api (by @HaxSam & @WillyJL) diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 43621de78..4b427b759 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -50,13 +50,20 @@ bool ducky_is_line_end(const char chr) { return (chr == ' ') || (chr == '\0') || (chr == '\r') || (chr == '\n'); } -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars) { +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers) { uint16_t keycode = ducky_get_keycode_by_name(param); if(keycode != HID_KEYBOARD_NONE) { return keycode; } - if((accept_chars) && (strlen(param) > 0)) { + if(accept_modifiers) { + uint16_t keycode = ducky_get_modifier_keycode_by_name(param); + if(keycode != HID_KEYBOARD_NONE) { + return keycode; + } + } + + if(strlen(param) > 0) { return BADUSB_ASCII_TO_KEY(bad_usb, param[0]) & 0xFF; } return 0; @@ -213,7 +220,7 @@ static int32_t ducky_parse_line(BadUsbScript* bad_usb, FuriString* line) { // Main key char next_char = *line_cstr; - key = modifiers | ducky_get_keycode(bad_usb, line_cstr, true); + key = modifiers | ducky_get_keycode(bad_usb, line_cstr, false); if(key == 0 && next_char) ducky_error(bad_usb, "No keycode defined for %s", line_cstr); diff --git a/applications/main/bad_usb/helpers/ducky_script_commands.c b/applications/main/bad_usb/helpers/ducky_script_commands.c index 6c6fe36c7..dbf9c1aef 100644 --- a/applications/main/bad_usb/helpers/ducky_script_commands.c +++ b/applications/main/bad_usb/helpers/ducky_script_commands.c @@ -92,7 +92,7 @@ static int32_t ducky_fnc_sysrq(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); bad_usb->hid->kb_press(bad_usb->hid_inst, KEY_MOD_LEFT_ALT | HID_KEYBOARD_PRINT_SCREEN); bad_usb->hid->kb_press(bad_usb->hid_inst, key); bad_usb->hid->release_all(bad_usb->hid_inst); @@ -196,7 +196,7 @@ static int32_t ducky_fnc_globe(BadUsbScript* bad_usb, const char* line, int32_t UNUSED(param); line = &line[ducky_get_command_len(line) + 1]; - uint16_t key = ducky_get_keycode(bad_usb, line, true); + uint16_t key = ducky_get_keycode(bad_usb, line, false); if(key == HID_KEYBOARD_NONE) { return ducky_error(bad_usb, "No keycode defined for %s", line); } diff --git a/applications/main/bad_usb/helpers/ducky_script_i.h b/applications/main/bad_usb/helpers/ducky_script_i.h index d735a8407..e5e0d645c 100644 --- a/applications/main/bad_usb/helpers/ducky_script_i.h +++ b/applications/main/bad_usb/helpers/ducky_script_i.h @@ -50,7 +50,7 @@ struct BadUsbScript { size_t string_print_pos; }; -uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_chars); +uint16_t ducky_get_keycode(BadUsbScript* bad_usb, const char* param, bool accept_modifiers); uint32_t ducky_get_command_len(const char* line); @@ -58,6 +58,8 @@ bool ducky_is_line_end(const char chr); uint16_t ducky_get_next_modifier_keycode_by_name(const char** param); +uint16_t ducky_get_modifier_keycode_by_name(const char* param); + uint16_t ducky_get_keycode_by_name(const char* param); uint16_t ducky_get_media_keycode_by_name(const char* param); diff --git a/applications/main/bad_usb/helpers/ducky_script_keycodes.c b/applications/main/bad_usb/helpers/ducky_script_keycodes.c index ce957bb4e..77cc324d3 100644 --- a/applications/main/bad_usb/helpers/ducky_script_keycodes.c +++ b/applications/main/bad_usb/helpers/ducky_script_keycodes.c @@ -131,6 +131,18 @@ uint16_t ducky_get_next_modifier_keycode_by_name(const char** param) { return HID_KEYBOARD_NONE; } +uint16_t ducky_get_modifier_keycode_by_name(const char* param) { + for(size_t i = 0; i < COUNT_OF(ducky_modifier_keys); i++) { + size_t key_cmd_len = strlen(ducky_modifier_keys[i].name); + if((strncmp(param, ducky_modifier_keys[i].name, key_cmd_len) == 0) && + (ducky_is_line_end(param[key_cmd_len]))) { + return ducky_modifier_keys[i].keycode; + } + } + + return HID_KEYBOARD_NONE; +} + uint16_t ducky_get_keycode_by_name(const char* param) { for(size_t i = 0; i < COUNT_OF(ducky_keys); i++) { size_t key_cmd_len = strlen(ducky_keys[i].name); From 8ef9a07608949513e4c30fb990da4b3195a8f89b Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 04:54:34 +0300 Subject: [PATCH 10/27] Subghz V2 Phoenix fully supported now With big thanks to all contributors 2022.08 - @Skorpionm 2025.07 - @xMasterX & @RocketGod-git --- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../helpers/subghz_txrx_create_protocol_key.c | 28 ++ .../helpers/subghz_txrx_create_protocol_key.h | 7 + .../subghz/scenes/subghz_scene_set_type.c | 30 +- lib/subghz/protocols/phoenix_v2.c | 280 +++++++++++++++++- lib/subghz/protocols/public_api.h | 16 + targets/f7/api_symbols.csv | 1 + 7 files changed, 352 insertions(+), 11 deletions(-) diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index a3dae60b8..e971bd056 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 { SetTypeSomfyTelis, SetTypeANMotorsAT4, SetTypeAlutechAT4N, + SetTypePhoenix_V2_433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, 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 813b706b6..5a7e07e0e 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.c @@ -384,6 +384,34 @@ bool subghz_txrx_gen_secplus_v1_protocol( return ret; } +bool subghz_txrx_gen_phoenix_v2_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt) { + SubGhzTxRx* txrx = context; + + bool res = false; + + txrx->transmitter = + subghz_transmitter_alloc_init(txrx->environment, SUBGHZ_PROTOCOL_PHOENIX_V2_NAME); + subghz_txrx_set_preset(txrx, preset_name, frequency, NULL, 0); + + if(txrx->transmitter && subghz_protocol_phoenix_v2_create_data( + subghz_transmitter_get_protocol_instance(txrx->transmitter), + txrx->fff_data, + serial, + cnt, + txrx->preset)) { + res = true; + } + + subghz_transmitter_free(txrx->transmitter); + + return res; +} + void subghz_txrx_gen_serial_gangqi(uint64_t* result_key) { uint64_t randkey = (uint64_t)rand(); uint16_t serial = (uint16_t)((randkey) & 0xFFFF); 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 fba7acb6f..7daa61b31 100644 --- a/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h +++ b/applications/main/subghz/helpers/subghz_txrx_create_protocol_key.h @@ -115,6 +115,13 @@ bool subghz_txrx_gen_came_atomo_protocol( uint32_t serial, uint16_t cnt); +bool subghz_txrx_gen_phoenix_v2_protocol( + void* context, + const char* preset_name, + uint32_t frequency, + uint32_t serial, + uint16_t cnt); + /** * Generate data SecPlus v2 protocol * diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 134d2e1d1..3f2fd9eff 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -20,6 +20,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSomfyTelis] = "Somfy Telis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", + [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", [SetTypeDoorHan_433_92] = "KL: DoorHan 433MHz", @@ -112,6 +113,7 @@ typedef enum { GenNiceFlorS, GenSecPlus1, GenSecPlus2, + GenPhoenixV2, } GenType; typedef struct { @@ -170,6 +172,10 @@ typedef struct { uint8_t btn; uint32_t cnt; } sec_plus_2; + struct { + uint32_t serial; + uint16_t cnt; + } phoenix_v2; }; } GenInfo; @@ -634,16 +640,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .type = GenCameAtomo, .mod = "AM650", .freq = 433920000, - .keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000, - .keeloq.cnt = 0x03}; + .came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000, + .came_atomo.cnt = 0x03}; break; case SetTypeCameAtomo868: gen_info = (GenInfo){ .type = GenCameAtomo, .mod = "AM650", .freq = 868350000, - .keeloq.serial = (key & 0x0FFFFFFF) | 0x10000000, - .keeloq.cnt = 0x03}; + .came_atomo.serial = (key & 0x0FFFFFFF) | 0x10000000, + .came_atomo.cnt = 0x03}; break; case SetTypeBFTMitto: gen_info = (GenInfo){ @@ -869,6 +875,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .sec_plus_2.btn = 0x68, .sec_plus_2.cnt = 0xE500000}; break; + case SetTypePhoenix_V2_433: + gen_info = (GenInfo){ + .type = GenPhoenixV2, + .mod = "AM650", + .freq = 433920000, + .phoenix_v2.serial = (key & 0x0FFFFFFF) | 0xB0000000, + .phoenix_v2.cnt = 0x025D}; + break; default: furi_crash("Not implemented"); break; @@ -976,6 +990,14 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { gen_info.sec_plus_2.btn, gen_info.sec_plus_2.cnt); break; + case GenPhoenixV2: + generated_protocol = subghz_txrx_gen_phoenix_v2_protocol( + subghz->txrx, + gen_info.mod, + gen_info.freq, + gen_info.phoenix_v2.serial, + gen_info.phoenix_v2.cnt); + break; default: furi_crash("Not implemented"); break; diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 9e88324c4..0229aeaeb 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -6,9 +6,9 @@ #include "../blocks/generic.h" #include "../blocks/math.h" -#define TAG "SubGhzProtocolPhoenixV2" +#include "../blocks/custom_btn_i.h" -//transmission only static mode +#define TAG "SubGhzProtocolPhoenixV2" static const SubGhzBlockConst subghz_protocol_phoenix_v2_const = { .te_short = 427, @@ -62,7 +62,7 @@ const SubGhzProtocolEncoder subghz_protocol_phoenix_v2_encoder = { const SubGhzProtocol subghz_protocol_phoenix_v2 = { .name = SUBGHZ_PROTOCOL_PHOENIX_V2_NAME, - .type = SubGhzProtocolTypeStatic, + .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, @@ -91,6 +91,138 @@ void subghz_protocol_encoder_phoenix_v2_free(void* context) { free(instance); } +// 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); + +bool subghz_protocol_phoenix_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolEncoderPhoenix_V2* instance = context; + instance->generic.btn = 0x1; + instance->generic.serial = serial; + instance->generic.cnt = cnt; + instance->generic.data_count_bit = 52; + + uint64_t local_data_rev = + (uint64_t)(((uint64_t)instance->generic.cnt << 40) | + ((uint64_t)instance->generic.btn << 32) | (uint64_t)instance->generic.serial); + + uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter( + 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); + + return SubGhzProtocolStatusOk == + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +// Get custom button code +static uint8_t subghz_protocol_phoenix_v2_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 0x3: + 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 0x3: + 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 0x3: + btn = 0x8; + break; + + default: + break; + } + } else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) { + switch(original_btn_code) { + case 0x1: + btn = 0x3; + break; + case 0x2: + btn = 0x3; + break; + case 0x4: + btn = 0x3; + break; + case 0x8: + btn = 0x3; + break; + case 0x3: + btn = 0x2; + break; + + default: + break; + } + } + + return btn; +} + /** * Generating an upload from data. * @param instance Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance @@ -107,6 +239,40 @@ static bool } else { instance->encoder.size_upload = size_upload; } + + uint8_t btn = instance->generic.btn; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Get custom button code + // This will override the btn variable if a custom button is set + btn = subghz_protocol_phoenix_v2_get_btn_code(); + + // Reconstruction of the data + if(instance->generic.cnt < 0xFFFF) { + 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 >= 0xFFFF) && (furi_hal_subghz_get_rolling_counter_mult() != 0)) { + instance->generic.cnt = 0; + } + + uint64_t local_data_rev = subghz_protocol_blocks_reverse_key( + instance->generic.data, instance->generic.data_count_bit + 4); + + uint16_t encrypted_counter = (uint16_t)subghz_protocol_phoenix_v2_encrypt_counter( + local_data_rev, instance->generic.cnt); + + instance->generic.data = subghz_protocol_blocks_reverse_key( + (uint64_t)(((uint64_t)encrypted_counter << 40) | ((uint64_t)btn << 32) | + (uint64_t)instance->generic.serial), + instance->generic.data_count_bit + 4); + //Send header instance->encoder.upload[index++] = level_duration_make(false, (uint32_t)subghz_protocol_phoenix_v2_const.te_short * 60); @@ -149,10 +315,22 @@ SubGhzProtocolStatus 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)) { ret = SubGhzProtocolStatusErrorEncoderGetUpload; 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 add Key"); + break; + } + instance->encoder.is_running = true; } while(false); @@ -274,16 +452,103 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t } } +static uint16_t subghz_protocol_phoenix_v2_encrypt_counter(uint64_t full_key, uint16_t counter) { + uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial + uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial + + uint8_t byte2 = (uint8_t)(counter >> 8); // First counter byte + uint8_t byte1 = (uint8_t)(counter & 0xFF); // Second counter byte + + // See decrypt function before reading these comments + for(int i = 0; i < 16; i++) { + // The key to reversing the process is that the MSB of the *current* byte2 + // tells us what the MSB of the *previous* byte1 was. This allows us to + // determine if the conditional XOR was applied before?. + uint8_t msb_of_prev_byte1 = byte2 & 0x80; + + if(msb_of_prev_byte1 == 0) { + // reverse the XOR. + byte2 ^= xor_key2; + byte1 ^= xor_key1; + } + + // Perform the bit shuffle in reverse + // Store the least significant bit (LSB) of the current byte1. + uint8_t lsb_of_current_byte1 = byte1 & 1; + + byte2 = (byte2 << 1) | lsb_of_current_byte1; + byte1 = (byte1 >> 1) | msb_of_prev_byte1; + } + + return (uint16_t)byte1 << 8 | byte2; +} + +static uint16_t subghz_protocol_phoenix_v2_decrypt_counter(uint64_t full_key) { + uint16_t encrypted_value = (uint16_t)((full_key >> 40) & 0xFFFF); + + uint8_t byte1 = (uint8_t)(encrypted_value >> 8); // First encrypted counter byte + uint8_t byte2 = (uint8_t)(encrypted_value & 0xFF); // Second encrypted counter byte + + uint8_t xor_key1 = (uint8_t)(full_key >> 24); // First byte of serial + uint8_t xor_key2 = (uint8_t)((full_key >> 16) & 0xFF); // Second byte of serial + + for(int i = 0; i < 16; i++) { + // Store the most significant bit (MSB) of byte1. + // The check `(msb_of_byte1 == 0)` will determine if we apply the XOR keys. + uint8_t msb_of_byte1 = byte1 & 0x80; + + // Store the least significant bit (LSB) of byte2. + uint8_t lsb_of_byte2 = byte2 & 1; + + // Perform a bit shuffle between the two bytes + byte2 = (byte2 >> 1) | msb_of_byte1; + byte1 = (byte1 << 1) | lsb_of_byte2; + + // Conditionally apply the XOR keys based on the original MSB of byte1. + if(msb_of_byte1 == 0) { + byte1 ^= xor_key1; + // The mask `& 0x7F` clears the MSB of byte2 after the XOR. + byte2 = (byte2 ^ xor_key2) & 0x7F; + } + } + + return (uint16_t)byte2 << 8 | byte1; +} + /** * Analysis of received data * @param instance Pointer to a SubGhzBlockGeneric* instance */ static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneric* instance) { + // 2022.08 - @Skorpionm + // 2025.07 - @xMasterX & @RocketGod-git + // Fully supported now, with button switch and add manually + // + // Key samples + // Full key example: 0xC63E01B9615720 - after subghz_protocol_blocks_reverse_key was applied + // Serial - B9615720 + // Button - 01 + // Encrypted -> Decrypted counters + // C63E - 025C + // BCC1 - 025D + // 3341 - 025E + // 49BE - 025F + // 99D3 - 0260 + // E32C - 0261 + uint64_t data_rev = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit + 4); + instance->serial = data_rev & 0xFFFFFFFF; - instance->cnt = (data_rev >> 40) & 0xFFFF; + instance->cnt = subghz_protocol_phoenix_v2_decrypt_counter(data_rev); instance->btn = (data_rev >> 32) & 0xF; + // encrypted cnt is (data_rev >> 40) & 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); } uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { @@ -321,12 +586,13 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou "%s %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" - "Btn:%X Cnt: 0x%04lX\r\n", + "Cnt: 0x%04lX\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), instance->generic.serial, - instance->generic.btn, - instance->generic.cnt); + instance->generic.cnt, + instance->generic.btn); } diff --git a/lib/subghz/protocols/public_api.h b/lib/subghz/protocols/public_api.h index d7ae21c2a..39c08e6aa 100644 --- a/lib/subghz/protocols/public_api.h +++ b/lib/subghz/protocols/public_api.h @@ -123,6 +123,22 @@ bool subghz_protocol_came_atomo_create_data( uint16_t cnt, SubGhzRadioPreset* preset); +/** + * Key generation from simple data. + * @param context Pointer to a SubGhzProtocolEncoderPhoenix_V2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param serial Serial number + * @param cnt Counter value, 16 bit + * @param preset Modulation, SubGhzRadioPreset + * @return true On success + */ +bool subghz_protocol_phoenix_v2_create_data( + void* context, + FlipperFormat* flipper_format, + uint32_t serial, + uint16_t cnt, + SubGhzRadioPreset* preset); + /** * New remote generation. * @param context Pointer to a SubGhzProtocolEncoderNiceFlorS instance diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index d0cea83e2..7696a05bc 100755 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3621,6 +3621,7 @@ Function,+,subghz_protocol_faac_slh_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_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*" Function,+,subghz_protocol_raw_gen_fff_data,void,"FlipperFormat*, const char*, const char*" Function,+,subghz_protocol_raw_get_sample_write,size_t,SubGhzProtocolDecoderRAW* From 06b29ddc1bedae78ab8605c66838b3ca068691b9 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 04:59:01 +0300 Subject: [PATCH 11/27] upd changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index beab8f799..8e9eff410 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## Main changes - Current API: 86.0 +* SubGHz: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) +* SubGHz: Reduce less popular freqs in default hopper preset, make it faster +* SubGHz: Marantec protocol implement crc verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: Keeloq: Comunello - add manually support * iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) * SubGHz: Rename and extend Alarms, Sensors, Cars ignore options (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) * SubGHz: V2 Phoenix show counter value From 30621b2fd7367115e868a31f7f9ed70448170d35 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 05:47:38 +0300 Subject: [PATCH 12/27] Update keeloq keys, motorline add manually support, readme spoiler alert Add keeloq keys: by @xMasterX & @RocketGod-git --- CHANGELOG.md | 22 +-- ReadMe.md | 12 +- .../main/subghz/helpers/subghz_custom_event.h | 1 + .../resources/subghz/assets/keeloq_mfcodes | 126 +++++++++--------- .../subghz/scenes/subghz_scene_set_type.c | 11 ++ lib/subghz/protocols/keeloq.c | 13 +- 6 files changed, 109 insertions(+), 76 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e9eff410..feb903787 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,25 @@ ## Main changes - Current API: 86.0 -* SubGHz: V2 Phoenix full support (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) -* SubGHz: Reduce less popular freqs in default hopper preset, make it faster -* SubGHz: Marantec protocol implement crc verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) -* SubGHz: Keeloq: Comunello - add manually support -* iButton: TM01x Dallas write support (PR #899 | by @Leptopt1los) -* SubGHz: Rename and extend Alarms, Sensors, Cars ignore options (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) +* SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) +* SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) +* SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) +* SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** +* SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: **Keeloq: Comunello - add manually support** +* iButton: **TM01x Dallas write support** (PR #899 | by @Leptopt1los) +* SubGHz: Rename and **extend Alarms, Sensors, Cars ignore options** (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) * SubGHz: V2 Phoenix show counter value -* SubGHz: Add keeloq ironlogic (aka il100) smart clone cloners support (thanks to Vitaly for RAWs) -* SubGHz: Fix CAME 24bit decoder +* SubGHz: **Add keeloq ironlogic (aka il100) smart clone cloners support** (thanks to Vitaly for RAWs) +* SubGHz: **Fix CAME 24bit decoder** * SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list -* SubGHz: Tune holtek ht12x to decode holtek only and not conflict with came 12bit +* SubGHz: **Tune holtek ht12x to decode holtek only** and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) * System: Loader - Fix misplaced ApplicationBeforeLoad events (PR #905 | by @WillyJL) * OFW PR 4210: Infrared: Add text scroll to remote buttons (by @956MB) * NFC: - - NFC Type 4 support + many other improvements (by @WillyJL) + - **NFC Type 4 support + many other improvements** (by @WillyJL) - New Type 4 Tag (NDEF on NTAG4xx / MIFARE DESFire) protocol, full support - New NTAG4xx (NTAG413 DNA / NTAG424 DNA) protocol, only detection and basic info support - NDEF parsing plugin supports Type 4 Tag protocol diff --git a/ReadMe.md b/ReadMe.md index 395222fff..eaa221ea7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -127,7 +127,7 @@ Before getting started: > - Battery percentage display with different styles `Settings -> Desktop -> Battery View` > - More games in Dummy Mode → click or hold any of arrow buttons > - Lock device with pin (or regular lock if pin not set) by holding UP button on main screen [(by an4tur0r)](https://github.com/DarkFlippers/unleashed-firmware/pull/107) -> - **BadKB** plugin [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (See in Applications → Tools) - (aka BadUSB via Bluetooth) +> - **BadKB** (BadUSB) [(by Willy-JL, ClaraCrazy, XFW contributors)](https://github.com/Flipper-XFW/Xtreme-Firmware/tree/dev/applications/main/bad_kb) - (Integrated into BadUSB app now!) - (aka BadUSB via Bluetooth) > - BadUSB → Keyboard layouts [(by rien > dummy-decoy)](https://github.com/dummy-decoy/flipperzero-firmware/tree/dummy_decoy/bad_usb_keyboard_layout) > - Custom community plugins and games added + all known working apps can be downloaded in extra pack in every release > - Other small fixes and changes throughout @@ -157,8 +157,9 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp > | 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 | | -> | Monarch | Jolly Motors | Magic_3 | Sheriff | | +> | Centurion | Gibidi | Magic_2 | Reff | Merlin | +> | Monarch | Jolly Motors | Magic_3 | Sheriff | Steelmate | +> | Motorline | Rosh | Pecinin | Rossi | | >
@@ -166,6 +167,9 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp Decoders/Encoders or emulation (+ programming mode) support made by @xMasterX
+- Roger (static 28 bit) with add manually support (by @xMasterX & @mishamyte) +- V2 Phoenix (Phox) (dynamic 52 bit) (by @xMasterX & @RocketGod-git) +- Marantec (static 49 bit) (add manually support and CRC verify) (by @xMasterX & @li0ard) - Feron (static 32 bit) - ReversRB2 / RB2M (static 64 bit) with add manually support - Marantec24 (static 24 bit) with add manually support @@ -174,7 +178,7 @@ Thanks to Official team (to their SubGHz Developer, Skorp) for implementing supp - 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) +- 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)
diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index e971bd056..cffad2929 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -75,6 +75,7 @@ typedef enum { SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, + SetTypeMotorline433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, diff --git a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes index 3f3e825af..62135c0b9 100644 --- a/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes +++ b/applications/main/subghz/resources/subghz/assets/keeloq_mfcodes @@ -1,63 +1,69 @@ Filetype: Flipper SubGhz Keystore File Version: 0 Encryption: 1 -IV: 4E 6F 20 66 75 72 69 20 63 68 65 63 6B 3F 21 3F -2F0767B5B190608EB032D12BFA937D760A77D08D37F851E940767F1915E97ACF -332F8DCCFDBF0485EC2EEED0C279F277E52A86A93BC5E4E96BE5F7276CC66713 -D9A02CC785FC0495063C424B0B1BAE7C120A2C24D4C0EE743F5D216718B16490 -4D9DD617090BDB100986B6987CAAC3652D2ADAB1AD9E368C5806D98562FF6B2F -28D21748FF3826FA13C785A6721CC5927C81EDAB0C5CF31C92EAFF12AA91608298485D8A3AB443640237372ADF0DDC49 -5058E12C0A41EDCB5C0812554F619DADFB6E895B94421952ECD9255A04EE5E1A -83A3EB8B22D94487A6B0F37856FB6AE9F42272BF25E1AE06DE03AA881A12D15F -D0E207DE64402B43ECD0C341216B6BCDC449508116E81D8ACDE7FA0BFBEA56F7 -6C4F723DE3B775D4C07E12ED3C369250B4D2089ADE2207816DED130D4B498CDF -B041911C56555E5F4676BF16819F61BF7A92402EB0427B8C2E7367B0AEA6B53C -1AD460260F20146A763BF6D4CD26DF5139EE29FFF8B53F6C5367EA779E1BEE56D5DFD872EA0268FE27204175925079AA -B1A9331AED36137CD078536A67775E2880D3CD7305373BC44A5649435E466AD2DC9FDE8AC1F572EF094D4B438C9509EA -105819300A9152F16E3478151799ECBBB7CCCE63DADA3F6C6D16D46830E1E035 -354186E04BC90D672F76A427FC1CD35C2EFAE8D4D1C36247FFB71ACB862A3601 -84B533148282D0D8121E5BBBBD39DE16F398365B015E02417ECC535C88EB9C57 -E899C9DC779F82E798EE923D257D5F051E1254DCDA2A6A955882727AA8E98ED8 -B8EC34F9B75E61D34E9C075A5316FAFC395E8FBA4F76B215620C5B5C76C53DB7BF316E53582161AD20F64CAB8E3347B2 -966C3B0873F48889B51D7D9DACC0674CBC95B06E231E30F235C826285D529A54 -370DED014764D7278E342D3AB631AB37D8F8A8FAE08987E03D7FC279DEEEB913 -2318A2DA42EEA2A35FFC9BDFBB244DF3FF6933010D74B3465336C0E37FFDC48A200568F8D6003AB215388734B8AC1F20 -475B35437FECEE9792F53A671252E78566AA9894DE7A4DEC9AED70834864E804E87478009F424CE1424C00F162BB03C5 -01CE6251ED9682BA6366075081167196CD740D346C4DAC4E0012C7951C475AE7 -CB225891F937CA491B711AA942B04C61C7CFA6A8E912D91D0906B401737E03B4 -F35D279815DEF19C9C5BC9ED55E7C1A0366E37DCD0A93E6E168B5A2602201C7B -3569D8DF2490797D40978F8325B94CC707383FEA1B46240BFDAECFEFB1F8176D -3D7BAF13573BBF3102757C68D52236638CC57126FF3795A604CFFA2D3F1B9C26 -B9102C87D7DBCF35463F38B6B80B70408968B6E01A47F6A7E8A3E87A0577B4ED -7673FAC14D94ABF72800A78E2DC4CAF2166FBB24719C22CFC1010492F4C87734 -1AF74DA07EA3A418EB86BB7ABAD6192B8E5A53F61B3E74CB898CB3EE4A7E244A -832D18C44062DDE856384E19D1417FA48D809C2CB2107CDEC5281943559791A6 -CD482A8FAB2A2CBE25A0B4A4788F274CA7095AA24508C00DBB78DD12BFB11C37 -EAC52E802DB76B51058752D7EFA91BCB1212AB96B589F9A88465195C1DE3242E -96CC75952A513AB5FE62A69AB6CDDA93C2156A3EA607C25B3201CE7284B3DAA9 -986E71EE87E860192141A1453929E575706E3FE72B7A9FEF5ACA696388649EB6 -FFF89FECC1C01FA3F266B95BDEF61A16F514E59599DAA07E908C604E9FE686C0 -ACC159D4AE78E26B5A1468F69D961028D0BF962D03E865415E7FE746553FEF15 -0FF46B2F9D4E907B9924675081D17C38C09957AA2F4C3C1F5568461DBA850F6301328CDC0FCEE83C7E8BA00CF8FC0F97 -7FD793C05E499739C3C4F8CC1D2D205A55928AB5BC60752A81D86DFBE30C50BD -CE444F4A1BEB38C9E93579E1B7FB3E90B4F85D8DA94DFC622272DED093A35192 -C7C31D8AB9D717FAF842F850A3E0D6B404EB9A980D9679362ABA90594B1957AB -1D48A6CFFBB6F14DD5BED1F8E74E6CC0292150546EDD76361792677434A8FE5F -F7335B8877DDF17F0737ECF77E2E6286E78534CE28E72077360E8E24D7750DFE -51051D9A8D5941661EBCF437B4640E6DA0B9B27518D761F3EF26BF7EABC691D4 -79F279733E18393FEDB50D654A0D0A532A64BED5ACBD13319439EEC007BC359C -646666FDB75D439C0427A9E3EF47F145DBD4FF5FE2E244909D74F075B24FF5A9B47E7AF98271210057D937A0E4B1F46D -DE7E814A2BD4D8823D9F2380EFAFFA1380A90391F87CBF24CE46BD46205EABAB -1335C4C3E819E942F5C212E9BEFAF5D984316C0A2FF6E9886886B565625618A9 -65386F906F18FF9C3A20AB57F3241D4975FE312ACDEB7FB1B91F2B816CAA46E7 -DF8A8B33782D56667F4C98F8F91B49B71A9E83AF015D8841986D41663233A0DC -27264455248878BB226FA1DED0922BD10313FF65F8A6A0E3CCDFB77890C838BB -43A08F784F36A3E8049BA63A401F3F15B3CA2ED263F8638595B5F22A0B081369 -F9F82F89C15AD970320E3D7A83B272EB00CD0ED657E4D230AB33C995859EA77F -70AD020D172E18E1011DF88E5F934F03F34DCE8148F8053B4FFA6F92CAC9FC93 -2B845F67BAB432CED64F2D68454A2B4B3BC46FFDC2A16D3340360C7BEA110BBB -B85F16A2370B278FDB3C85E6455B8DA239D6413B727839DEFBCB5628A6C747266291AB9D9F4F5DA1826B219C1A29F956 -FFB7B10D96F241FDB994008AF85EC85D147A97AA599D05F5EE1BB2FC27644A26 -0BD42CA312CBBCAE556AA0159EC2CC2FA70BBB00D8DF7B63BBEA60A282481AED -9CC73810056A21EA6F311B01BA7F44655A075D1F60947FBC9B6924C3BD0ED819 -024FCB96977ECA1C0D4B9C7C461361329D96E5AFF315124FEFC0DF2A400DE312F45D602DB40CD4EB088F144EB0B8DF41 +IV: 46 75 72 72 79 20 52 6F 63 6B 65 74 21 21 21 30 +05176EEFAC177FE261FE3EB5C8E103BE7CF9F2FEB32BDD6BB63D22EE9C17B9D2 +B645E3CAC0D5E26891249D326BCEB09850E4FB8F8E86A466E97E83437A9E0041 +AA4255FFA1ADE8FB840F80A93F8F1A2D1E39051131D24DE7258D66A8CF2066CF +13ACA390FD5254B024084D5D1F41B8DDF5304FF00C3C85A9C26CD13A7A268654 +4CFBF498D5E2C85496985E83D91B0F4229A925E16A90C6712750032C3699EE0AA5D04123E579B6121573FC61766E89AD +93DADC2AE4235470E171E0E85D24D04A84C37187284C38D1CBB48666FDA8CD6C +DB13D8CCC0CB07685F29F33AE07DA2FD14C2AE4F4D001DB88465D5CFE8CFDAA9 +E51CD1B5074B63D26E274218A0AB3B2E435454EE094DCA5679F35477658A72F9 +10AFD5FD9C296E67EDD9504A60BA9EF84556F40213DEC4DE44F99B088BCC6A57 +EF7AA55F6A473DE093D648240D5FCEB05F8B3295DC37B3E83239A4AF320CD688 +A22892E71B9D0D7FAF92B27C724E76C4A6824DBE5F083F1006D11E42D153C4AC98D0A11C6A8D62F5921A24ECC7437485 +7A25416E390D81DA68A59C3BA30D4B7FC8269B5E0DAF77CA3A857B6F478A050585918485AEE72D375F02D177CB296E31 +94004BA0BB1E47965E60025949EF4CC2738C463F57C97FD2A89C76CCCDEA5397 +111CB1C19863A0165521D974F838CE718DA07948A8D9A8A7490E75032A62ECA2 +17B6E27C69FA002F6CF23D719DFE595140BEFA5083D12E774CF89E2CED53D68D +73311E0FF8ABB3E9461AD14A4F52791647A50E2102D3B74188A73C35BC14EB55 +54E15840A6A6DCA85275E38E4218EE2B539E9E468E24C49428DA363C955C5FC81ACEE79EEB941B83EE4147A0817043BD +7D0FBB417B99B3C6AB18C7B2DC82582D2DCD1E10515028874E73254188F7FEE9 +3F6E89BBCC133B85945234A8201539ECD8796909CC81FE67673F8DE1ECA63045 +39554C0DC1C3694FAAFF65537FF710D9593B7B461E011FC39D014F253F0432533A40276D8259AFD8C957A378237D574F +E60F6CD7063B85F0F20ACB7E7A42B03DE4A9F6CCA54CB7F036AFA23A27D3E9E006BD523E5356260AA78206D9276E6E57 +9EB252EDA9352B966EC4F053D5B013772361D2AD4B217EF33F46A5CEC97A00F3 +AA6773E79BC6D76314BB523FDF203358E01ECB2BBCF3B5DD1EBD043663C74B05 +29B29A50F3F27F4D8C7B0FADA98CC004A7871078DAD1CBAC4846862C3DF82E02 +6E3A479D4334FF05606899B0383116125056A316621B279F904A02B842918C59 +3991732015F4A213E9912E34AC92515D88010C07DA0B118AD6F64A05DC38D2C5 +550B1866F7493C75812DF85DDADC38AF21D9B58189E4EE99A021328523881A9D +77960CA031D28362586100F17DF94FF4E7D6EFAFAF23952887F9DF0507825A99 +01E6FC89E97B7729BF4D1ED8041F69005181BF3639F939C5833B009E96B9F2F7 +D1CC7C536706ECFC5826C8933135D2B110996F1CB13388A702B8453DA40E40AD +B64D2F1E1A80E6DAB92283A512B40DB7FFC519F394AA94CC86C8532F69949723 +6399409A0AC0298DEDA76037C83042FC0870132CFF7F82E54AD0966BE16AC882 +D310536FA78F95BB0B408676990AA937117717BADE9D3B975C0ECE10FB586A1B +A8149C0581DCC291D037E96EF321DB6214BD7CB25F1696226A9FE750AA23B334 +BA3BEBD564D8F571202CD6FE89BC33F89C8E01C03AE0814F2BEF37C33CE874B4 +88CD81AC7605A7F6EFF85FD62C65E0C9945335CFC085B92B27B69648C6E5BF6B +8057C7CB5071DFFFAE4804FD9EC1EC1D3F54D06514906A34B17F6B6CB45A9D473992DF6BC8A9F9E146E39D6163209CC6 +9ABC8814C8FD1AB254374150177616F5C7B43049473C84329BEC855578B96002 +8BCA39A498B00245C71D94E3160CEE8ACA5BEB18AE0AD64A385AFCC018E99744 +5AD75C51CA5AE5FA9BBC6A41576C745F265CC28FC4DA2AD230B6692CF151FD61 +E86092E04CD72D874A92DE838035E811E75E411049C0A7BD0FE2AA9C802BE5AB +CE70ADB22E85747FDC064F0B5974385CD57D41D376CE1C7490C1BEC8A3FC5A7A +8F096E0A11682DB315825213D3DB5D725555C1CDF444169EB919E47E0F0FA6F7 +AD9C9A694D807BA77E5A54B248A88B55000757203D931506255BF8F4215C00D3 +F0E804B6C6B6E91916CB73EB44FB2D1992400BC90ED8B22DF5D038317588341207D74E08C00E529DF2CF2A64F2C7C0FF +72212FCEED35E9C3A176B67DCDB84B284F4DFDCD0ECE8D3F6089C58C2B8A616C +000F9F746BFB47FC10B23E3F08C2A84BCB3870D0C5AE974472849699884BC929 +7B8F9AB04E5F86D6DDCF6164A25EA927788A03F57977FC5C55E1D565279B09C4 +0E9CDCD07D1D4F1429E59F81B524960A75F19A464798C7E822E52728AC83784A +F2DE2B108A1476BB6F85DD3CCB0F0527627B45179092BA7A56D5971490E3875C +7F307358D988FEA12648739F58DD249EBDF0B1C44B73BA547C50EB576C071DAE +2DFBA988592CEF3B62A76183DBA727E734359B89F53AFF3160441EF8709FC633 +57F7DC38DDC87C19CE956BC44C638DEF34D814A7BAB0AC8AD61855143FD984FD +A8AADB687251FA6AC2BBC8EF1E3FA621893293DFBD8C1D07971BF82F22A00DC3 +65AEA1EE34E316C769E551AC2309D07FC2ED92EA044674E3A99CD7B543C730EB +968ECC790E5590E7EB22AFD3546C28F4EB87EA4CEE35F72DDFE7153F74611EAA +0F937930D4E1BDF0B729277CF94A47064BCB959938C70CDB3AC3C65DA68DA1FB +A8AB66375D59E112104CD81B819D618BE43D6A6F159BAD35583653EF3547D25D +A81D5DE2102F05D50750DC37C26E9C9502FA89EF98A2EB1EA546EE48C628E9C4 +EAFDE0A8936AF8EF718027937BC17CEF691E570996B403CF4762240D267EB305 +C48686348F0A94B07BC60AB825C1A0791C20DBBDD7DAE0ED47E8A7FBD9334EACF8E33DCEC36963E87929260DF769520B +493D53BD7BB2B3E081AE793A3BADB3AB0F33C95B83677715D6DE2922F2BEC892 +63FFD3D8CAB980E45D49253A69C99A6813CBE6013992EFBC862173BAD0E26373 +2EF88F43C5A76EC87E02B780585B10957F4EA386F96710FAB98BC2C1E214DBFA +A021CFA0E72AADFD75BC67FBE9345082B0A8B31782E933E81196F84B1797D83E8B2F81E1CF5C3F026D11B9DFC95222E2 diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index 3f2fd9eff..b6ff75768 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -30,6 +30,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeComunello868] = "KL: Comunello 868MHz", [SetTypeAllmatic433] = "KL: Allmatic 433MHz", [SetTypeAllmatic868] = "KL: Allmatic 868MHz", + [SetTypeMotorline433] = "KL: Motorline 433MHz", [SetTypeCenturion433] = "KL: Centurion 433MHz", [SetTypeMonarch433] = "KL: Monarch 433MHz", [SetTypeJollyMotors433] = "KL: Jolly Mot. 433MHz", @@ -680,6 +681,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .somfy_telis.btn = 0x02, .somfy_telis.cnt = 0x03}; break; + case SetTypeMotorline433: + gen_info = (GenInfo){ + .type = GenKeeloq, + .mod = "AM650", + .freq = 433920000, + .keeloq.serial = key & 0x0FFFFFFF, + .keeloq.btn = 0x01, + .keeloq.cnt = 0x03, + .keeloq.manuf = "Motorline"}; + break; case SetTypeDoorHan_433_92: gen_info = (GenInfo){ .type = GenKeeloq, diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index a774e5825..e1ccf8c2f 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -237,9 +237,15 @@ static bool subghz_protocol_keeloq_gen_data( (strcmp(instance->manufacture_name, "Mutanco_Mutancode") == 0) || (strcmp(instance->manufacture_name, "Came_Space") == 0) || (strcmp(instance->manufacture_name, "Genius_Bravo") == 0) || - (strcmp(instance->manufacture_name, "GSN") == 0)) { + (strcmp(instance->manufacture_name, "GSN") == 0) || + (strcmp(instance->manufacture_name, "Rosh") == 0) || + (strcmp(instance->manufacture_name, "Rossi") == 0) || + (strcmp(instance->manufacture_name, "Pecinin") == 0) || + (strcmp(instance->manufacture_name, "Steelmate") == 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 decrypt = btn << 28 | (instance->generic.serial & 0xFFF) << 16 | instance->generic.cnt; } else if( @@ -249,9 +255,12 @@ static bool subghz_protocol_keeloq_gen_data( // Nice Smilo, MHouse, JCM -> 8bit serial - simple learning decrypt = btn << 28 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt; - } else if(strcmp(instance->manufacture_name, "Beninca") == 0) { + } else if( + (strcmp(instance->manufacture_name, "Beninca") == 0) || + (strcmp(instance->manufacture_name, "Merlin") == 0)) { decrypt = btn << 28 | (0x000) << 16 | instance->generic.cnt; // Beninca / Allmatic -> no serial - simple XOR + // Merlin -> no serial - simple XOR } else if(strcmp(instance->manufacture_name, "Centurion") == 0) { decrypt = btn << 28 | (0x1CE) << 16 | instance->generic.cnt; // Centurion -> no serial in hop, uses fixed value 0x1CE - normal learning From 1bed4d29cba326285f3c9942c249aa45991b7cef Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:01:36 +0300 Subject: [PATCH 13/27] Nero Radio static - better parsing --- lib/subghz/protocols/nero_radio.c | 42 +++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 7e787ffd0..d7822ac1c 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -385,6 +385,36 @@ SubGhzProtocolStatus } } +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_nero_radio_parse_data(SubGhzBlockGeneric* instance) { + // Key samples from unit tests + // 57250501049DD3 + // 57250502049D13 + // + // Samples from remote + // 36E4E80104A644 + // 36E4E80204A684 + // 36E4E80304A604 + // 36E4E80404A6E4 + + // possible contents + // serial button serial/const crc?? + // 5725050 1 049D D3 + // 5725050 2 049D 13 + // 36E4E80 1 04A6 44 + // 36E4E80 2 04A6 84 + // 36E4E80 3 04A6 04 + // 36E4E80 4 04A6 E4 + + // serial is larger than uint32 can't fit into serial field + // using data2 var since its uint64_t + instance->btn = (instance->data >> 24) & 0xF; + instance->data_2 = ((instance->data >> 28) << 16) | ((instance->data >> 8) & 0xFFFF); +} + void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* output) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; @@ -398,15 +428,23 @@ void subghz_protocol_decoder_nero_radio_get_string(void* context, FuriString* ou uint32_t code_found_reverse_hi = code_found_reverse >> 32; uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff; + subghz_protocol_nero_radio_parse_data(&instance->generic); + furi_string_cat_printf( output, "%s %dbit\r\n" "Key:0x%lX%08lX\r\n" - "Yek:0x%lX%08lX\r\n", + "Yek:0x%lX%08lX\r\n" + "Sn: 0x%llX \r\n" + "CRC?: 0x%02X\r\n" + "Btn: %X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, code_found_hi, code_found_lo, code_found_reverse_hi, - code_found_reverse_lo); + code_found_reverse_lo, + instance->generic.data_2, + (uint8_t)(instance->generic.data & 0xFF), + instance->generic.btn); } From 3c3d06bae093925a6158d1ce437f33ce06b97e4e Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:02:05 +0300 Subject: [PATCH 14/27] Roger that --- .../main/subghz/helpers/subghz_custom_event.h | 3 +- .../subghz/scenes/subghz_scene_set_type.c | 11 + lib/subghz/protocols/protocol_items.c | 1 + lib/subghz/protocols/protocol_items.h | 1 + lib/subghz/protocols/roger.c | 442 ++++++++++++++++++ lib/subghz/protocols/roger.h | 109 +++++ 6 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 lib/subghz/protocols/roger.c create mode 100644 lib/subghz/protocols/roger.h diff --git a/applications/main/subghz/helpers/subghz_custom_event.h b/applications/main/subghz/helpers/subghz_custom_event.h index cffad2929..9fc607889 100644 --- a/applications/main/subghz/helpers/subghz_custom_event.h +++ b/applications/main/subghz/helpers/subghz_custom_event.h @@ -75,7 +75,6 @@ typedef enum { SetTypeANMotorsAT4, SetTypeAlutechAT4N, SetTypePhoenix_V2_433, - SetTypeMotorline433, SetTypeHCS101_433_92, SetTypeDoorHan_315_00, SetTypeDoorHan_433_92, @@ -88,6 +87,7 @@ typedef enum { SetTypeCenturion433, SetTypeMonarch433, SetTypeJollyMotors433, + SetTypeMotorline433, SetTypeSommer_FM_434, SetTypeSommer_FM_868, SetTypeSommer_FM238_434, @@ -130,6 +130,7 @@ typedef enum { SetTypeMarantec24_868, SetTypeMarantec_433, SetTypeMarantec_868, + SetTypeRoger_433, SetTypeLinear_300_00, // SetTypeNeroSketch, //Deleted in OFW // SetTypeNeroRadio, //Deleted in OFW diff --git a/applications/main/subghz/scenes/subghz_scene_set_type.c b/applications/main/subghz/scenes/subghz_scene_set_type.c index b6ff75768..b986c2c5d 100644 --- a/applications/main/subghz/scenes/subghz_scene_set_type.c +++ b/applications/main/subghz/scenes/subghz_scene_set_type.c @@ -20,6 +20,7 @@ static const char* submenu_names[SetTypeMAX] = { [SetTypeSomfyTelis] = "Somfy Telis 433MHz", [SetTypeANMotorsAT4] = "AN-Motors AT4 433MHz", [SetTypeAlutechAT4N] = "Alutech AT4N 433MHz", + [SetTypeRoger_433] = "Roger 433MHz", [SetTypePhoenix_V2_433] = "V2 Phoenix 433MHz", [SetTypeHCS101_433_92] = "KL: HCS101 433MHz", [SetTypeDoorHan_315_00] = "KL: DoorHan 315MHz", @@ -286,6 +287,16 @@ bool subghz_scene_set_type_on_event(void* context, SceneManagerEvent event) { .data.bits = 24, .data.te = 0}; break; + case SetTypeRoger_433: + gen_info = (GenInfo){ + .type = GenData, + .mod = "AM650", + .freq = 433920000, + .data.name = SUBGHZ_PROTOCOL_ROGER_NAME, + .data.key = (key & 0xFFFF000) | 0x0000101, // button code 0x1 and (crc?) is 0x01 + .data.bits = 28, + .data.te = 0}; + break; case SetTypeLinear_300_00: gen_info = (GenInfo){ .type = GenData, diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index c73923c7a..465585d77 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -53,6 +53,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = { &subghz_protocol_hay21, &subghz_protocol_revers_rb2, &subghz_protocol_feron, + &subghz_protocol_roger, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index 6165d748a..4f63b030e 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -54,3 +54,4 @@ #include "hay21.h" #include "revers_rb2.h" #include "feron.h" +#include "roger.h" diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c new file mode 100644 index 000000000..d14547876 --- /dev/null +++ b/lib/subghz/protocols/roger.c @@ -0,0 +1,442 @@ +#include "roger.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 "SubGhzProtocolRoger" + +static const SubGhzBlockConst subghz_protocol_roger_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 270, + .min_count_bit_for_found = 28, +}; + +struct SubGhzProtocolDecoderRoger { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; +}; + +struct SubGhzProtocolEncoderRoger { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; +}; + +typedef enum { + RogerDecoderStepReset = 0, + RogerDecoderStepSaveDuration, + RogerDecoderStepCheckDuration, +} RogerDecoderStep; + +const SubGhzProtocolDecoder subghz_protocol_roger_decoder = { + .alloc = subghz_protocol_decoder_roger_alloc, + .free = subghz_protocol_decoder_roger_free, + + .feed = subghz_protocol_decoder_roger_feed, + .reset = subghz_protocol_decoder_roger_reset, + + .get_hash_data = subghz_protocol_decoder_roger_get_hash_data, + .serialize = subghz_protocol_decoder_roger_serialize, + .deserialize = subghz_protocol_decoder_roger_deserialize, + .get_string = subghz_protocol_decoder_roger_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_roger_encoder = { + .alloc = subghz_protocol_encoder_roger_alloc, + .free = subghz_protocol_encoder_roger_free, + + .deserialize = subghz_protocol_encoder_roger_deserialize, + .stop = subghz_protocol_encoder_roger_stop, + .yield = subghz_protocol_encoder_roger_yield, +}; + +const SubGhzProtocol subghz_protocol_roger = { + .name = SUBGHZ_PROTOCOL_ROGER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Send, + + .decoder = &subghz_protocol_roger_decoder, + .encoder = &subghz_protocol_roger_encoder, +}; + +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolEncoderRoger* instance = malloc(sizeof(SubGhzProtocolEncoderRoger)); + + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 10; + instance->encoder.size_upload = 256; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void subghz_protocol_encoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + free(instance->encoder.upload); + free(instance); +} + +// Get custom button code +static uint8_t subghz_protocol_roger_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; +} + +/** + * Generating an upload from data. + * @param instance Pointer to a SubGhzProtocolEncoderRoger instance + */ +static void subghz_protocol_encoder_roger_get_upload(SubGhzProtocolEncoderRoger* instance) { + furi_assert(instance); + size_t index = 0; + + uint8_t btn = instance->generic.btn; + + // Save original button for later use + if(subghz_custom_btn_get_original() == 0) { + subghz_custom_btn_set_original(btn); + } + + // Get custom button code + // This will override the btn variable if a custom button is set + btn = subghz_protocol_roger_get_btn_code(); + + // If CRC is not == button - transmit as is, no custom button allowed + if((instance->generic.data & 0xFF) == instance->generic.btn) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + btn; + } + + // 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_roger_const.te_long); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_short); + } + } else { + // Send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)subghz_protocol_roger_const.te_short); + if(i == 1) { + //Send gap if bit was last + instance->encoder.upload[index++] = level_duration_make( + false, (uint32_t)subghz_protocol_roger_const.te_short * 19); + } else { + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)subghz_protocol_roger_const.te_long); + } + } + } + + instance->encoder.size_upload = index; + return; +} + +/** + * Analysis of received data + * @param instance Pointer to a SubGhzBlockGeneric* instance + */ +static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* instance) { + // Roger Decoder + // 2025.07 - @xMasterX (MMX) + + // Key samples + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 CRC: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 CRC: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 CRC: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 CRC: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 CRC: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 CRC: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 CRC: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 CRC: 0x08 + + instance->serial = instance->data >> 12; + instance->btn = (instance->data >> 8) & 0xF; + + // 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); +} + +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolEncoderRoger* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_roger_const.min_count_bit_for_found); + if(ret != SubGhzProtocolStatusOk) { + break; + } + //optional parameter parameter + 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); + + 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 add Key"); + break; + } + + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +void subghz_protocol_encoder_roger_stop(void* context) { + SubGhzProtocolEncoderRoger* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_roger_yield(void* context) { + SubGhzProtocolEncoderRoger* 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; +} + +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderRoger* instance = malloc(sizeof(SubGhzProtocolDecoderRoger)); + instance->base.protocol = &subghz_protocol_roger; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_roger_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + free(instance); +} + +void subghz_protocol_decoder_roger_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + instance->decoder.parser_step = RogerDecoderStepReset; +} + +void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + switch(instance->decoder.parser_step) { + case RogerDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3)) { + //Found GAP + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } + break; + case RogerDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = RogerDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + case RogerDecoderStepCheckDuration: + if(!level) { + // Bit 1 is long and short timing = 1000us HIGH (te_last) and 500us LOW + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + // Bit 0 is short and long timing = 500us HIGH (te_last) and 1000us LOW + } else if( + (DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta) && + (DURATION_DIFF(duration, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = RogerDecoderStepSaveDuration; + } else if( + // End of the key + DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < + subghz_protocol_roger_const.te_delta * 3) { + //Found next GAP and add bit 1 or 0 + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } + if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_short) < + subghz_protocol_roger_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + // If got full 28 bits key reading is finished + if(instance->decoder.decode_count_bit == + subghz_protocol_roger_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 = RogerDecoderStepReset; + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + } else { + instance->decoder.parser_step = RogerDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus subghz_protocol_decoder_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + return subghz_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, subghz_protocol_roger_const.min_count_bit_for_found); +} + +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderRoger* instance = context; + + subghz_protocol_roger_check_remote_controller(&instance->generic); + + furi_string_cat_printf( + output, + "%s %db\r\n" + "Key: 0x%07lX\r\n" + "Serial: 0x%04lX\r\n" + "CRC: 0x%02lX\r\n" + "Btn: %01X", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data & 0xFFFFFFF), + instance->generic.serial, + (uint32_t)(instance->generic.data & 0xFF), + instance->generic.btn); +} diff --git a/lib/subghz/protocols/roger.h b/lib/subghz/protocols/roger.h new file mode 100644 index 000000000..c279164f9 --- /dev/null +++ b/lib/subghz/protocols/roger.h @@ -0,0 +1,109 @@ +#pragma once + +#include "base.h" + +#define SUBGHZ_PROTOCOL_ROGER_NAME "Roger" + +typedef struct SubGhzProtocolDecoderRoger SubGhzProtocolDecoderRoger; +typedef struct SubGhzProtocolEncoderRoger SubGhzProtocolEncoderRoger; + +extern const SubGhzProtocolDecoder subghz_protocol_roger_decoder; +extern const SubGhzProtocolEncoder subghz_protocol_roger_encoder; +extern const SubGhzProtocol subghz_protocol_roger; + +/** + * Allocate SubGhzProtocolEncoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolEncoderRoger* pointer to a SubGhzProtocolEncoderRoger instance + */ +void* subghz_protocol_encoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolEncoderRoger. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_free(void* context); + +/** + * Deserialize and generating an upload to send. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_encoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Forced transmission stop. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + */ +void subghz_protocol_encoder_roger_stop(void* context); + +/** + * Getting the level and duration of the upload to be loaded into DMA. + * @param context Pointer to a SubGhzProtocolEncoderRoger instance + * @return LevelDuration + */ +LevelDuration subghz_protocol_encoder_roger_yield(void* context); + +/** + * Allocate SubGhzProtocolDecoderRoger. + * @param environment Pointer to a SubGhzEnvironment instance + * @return SubGhzProtocolDecoderRoger* pointer to a SubGhzProtocolDecoderRoger instance + */ +void* subghz_protocol_decoder_roger_alloc(SubGhzEnvironment* environment); + +/** + * Free SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_free(void* context); + +/** + * Reset decoder SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + */ +void subghz_protocol_decoder_roger_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void subghz_protocol_decoder_roger_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @return hash Hash sum + */ +uint8_t subghz_protocol_decoder_roger_get_hash_data(void* context); + +/** + * Serialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger 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_roger_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data SubGhzProtocolDecoderRoger. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + subghz_protocol_decoder_roger_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a SubGhzProtocolDecoderRoger instance + * @param output Resulting text + */ +void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output); From bddebff1341592c18211d1c72563970e66ea80f3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:03:03 +0300 Subject: [PATCH 15/27] upd changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index feb903787..bcc118151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ * SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) * SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) * SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) +* SubGHz: Nero Radio static parse and display more data * SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** * SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) * SubGHz: **Keeloq: Comunello - add manually support** From 269cbd66e1b683cf4fb879bf55be41e0386d5e39 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:14:31 +0300 Subject: [PATCH 16/27] A little better naming for display in v2phox --- lib/subghz/protocols/phoenix_v2.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 0229aeaeb..a6a8a7108 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -583,12 +583,11 @@ void subghz_protocol_decoder_phoenix_v2_get_string(void* context, FuriString* ou subghz_protocol_phoenix_v2_check_remote_controller(&instance->generic); furi_string_cat_printf( output, - "%s %dbit\r\n" + "V2 Phoenix %dbit\r\n" "Key:%05lX%08lX\r\n" "Sn:0x%07lX \r\n" "Cnt: 0x%04lX\r\n" "Btn: %X\r\n", - instance->generic.protocol_name, instance->generic.data_count_bit, (uint32_t)(instance->generic.data >> 32) & 0xFFFFFFFF, (uint32_t)(instance->generic.data & 0xFFFFFFFF), From 3b29bd65088046bca065f39a945f8b5ab94655a3 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Sat, 12 Jul 2025 03:57:14 +0300 Subject: [PATCH 17/27] Roger decoder allow bigger gap and extend buttons functionality --- lib/subghz/protocols/roger.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/subghz/protocols/roger.c b/lib/subghz/protocols/roger.c index d14547876..7b57cbab4 100644 --- a/lib/subghz/protocols/roger.c +++ b/lib/subghz/protocols/roger.c @@ -178,10 +178,17 @@ 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(); - // If CRC is not == button - transmit as is, no custom button allowed + // 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) { instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | btn; + } else if(((instance->generic.data & 0xFF) == 0x23) && btn == 0x1) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + 0x20; + } else if(((instance->generic.data & 0xFF) == 0x20) && btn == 0x2) { + instance->generic.data = (uint64_t)instance->generic.serial << 12 | ((uint64_t)btn << 8) | + 0x23; } // Send key and GAP @@ -226,14 +233,14 @@ static void subghz_protocol_roger_check_remote_controller(SubGhzBlockGeneric* in // 2025.07 - @xMasterX (MMX) // Key samples - // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 CRC: 0x20 - // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 CRC: 0x23 - // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 CRC: 0x01 - // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 CRC: 0x02 - // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 CRC: 0x01 - // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 CRC: 0x04 - // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 CRC: 0x02 - // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 CRC: 0x08 + // 0010001111111001 0001 00100000 // S/N: 0x23F9 Btn: 0x1 End: 0x20 + // 0010001111111001 0010 00100011 // S/N: 0x23F9 Btn: 0x2 End: 0x23 + // 0101011001010110 0001 00000001 // S/N: 0x5656 Btn: 0x1 End: 0x01 + // 0101011001010110 0010 00000010 // S/N: 0x5656 Btn: 0x2 End: 0x02 + // 0000110111111110 0001 00000001 // S/N: 0x0DFE Btn: 0x1 End: 0x01 + // 0000110111111110 0100 00000100 // S/N: 0x0DFE Btn: 0x4 End: 0x04 + // 0000110111111110 0010 00000010 // S/N: 0x0DFE Btn: 0x2 End: 0x02 + // 0000110111111110 1000 00001000 // S/N: 0x0DFE Btn: 0x8 End: 0x08 instance->serial = instance->data >> 12; instance->btn = (instance->data >> 8) & 0xF; @@ -330,7 +337,7 @@ void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint switch(instance->decoder.parser_step) { case RogerDecoderStepReset: if((!level) && (DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < - subghz_protocol_roger_const.te_delta * 3)) { + subghz_protocol_roger_const.te_delta * 5)) { //Found GAP instance->decoder.decode_data = 0; instance->decoder.decode_count_bit = 0; @@ -365,7 +372,7 @@ void subghz_protocol_decoder_roger_feed(void* context, bool level, volatile uint } else if( // End of the key DURATION_DIFF(duration, subghz_protocol_roger_const.te_short * 19) < - subghz_protocol_roger_const.te_delta * 3) { + subghz_protocol_roger_const.te_delta * 5) { //Found next GAP and add bit 1 or 0 if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_roger_const.te_long) < subghz_protocol_roger_const.te_delta)) { @@ -431,7 +438,7 @@ void subghz_protocol_decoder_roger_get_string(void* context, FuriString* output) "%s %db\r\n" "Key: 0x%07lX\r\n" "Serial: 0x%04lX\r\n" - "CRC: 0x%02lX\r\n" + "End: 0x%02lX\r\n" "Btn: %01X", instance->generic.protocol_name, instance->generic.data_count_bit, From ac6621cdcbdbfce9f90c64b907c8ef943e54bd41 Mon Sep 17 00:00:00 2001 From: MX <10697207+xMasterX@users.noreply.github.com> Date: Wed, 16 Jul 2025 02:38:19 +0300 Subject: [PATCH 18/27] upd changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc118151..e33b7843f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,17 +3,17 @@ * SubGHz: **Roger (static 28 bit) with add manually support** (by @xMasterX & @mishamyte) * SubGHz: **V2 Phoenix full support** (button switch, add manually, counter decrypt/encrypt) (by @xMasterX & @RocketGod-git, original code by @Skorpionm) * SubGHz: **Keeloq: Add support for - Motorline (with add manually support), Rosh, Pecinin, Rossi, Merlin, Steelmate** (by @xMasterX & @RocketGod-git) -* SubGHz: Nero Radio static parse and display more data +* SubGHz: **Nero Radio static parse** and display more data * SubGHz: Reduce less popular freqs in default hopper preset, **make it faster** -* SubGHz: **Marantec protocol implement crc verification display and add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) +* SubGHz: **Marantec protocol implement CRC verification display and Add manually support** (by @xMasterX & @li0ard, original code by @Skorpionm) * SubGHz: **Keeloq: Comunello - add manually support** * iButton: **TM01x Dallas write support** (PR #899 | by @Leptopt1los) * SubGHz: Rename and **extend Alarms, Sensors, Cars ignore options** (Alarms: Hollarm, GangQi | Cars: Kia, Starline, ScherKhan | Sensors: Magellan, Honeywell, Honeywell WDB (doorbells), Legrand (doorbells), Feron (RGB lights)) -* SubGHz: V2 Phoenix show counter value -* SubGHz: **Add keeloq ironlogic (aka il100) smart clone cloners support** (thanks to Vitaly for RAWs) +* SubGHz: V2 Phoenix show counter value (upd: see above, now decrypted) +* SubGHz: **Add Keeloq IronLogic (aka IL100) smart clone remote copiers support** (thanks to Vitaly for RAWs) * SubGHz: **Fix CAME 24bit decoder** * SubGHz: Add 462.750 MHz & 868.46 MHz to default subghz freqs list -* SubGHz: **Tune holtek ht12x to decode holtek only** and not conflict with came 12bit +* SubGHz: **Tune Holtek HT12x to decode Holtek only** and not conflict with came 12bit * SubGHz: Fix Rename scene bug, that was replacing file name with random name when Rename is opened then closed then opened again * Display: Backlight option "always on" and RGB bug removed (PR #900 | by @Dmitry422) * NFC: Ultralight C - Attempt of authentication with default key (PR #898 | by @mishamyte) From b33456d243f79118ee3f8414ebeced20c725627c Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sun, 20 Jul 2025 01:46:11 +0100 Subject: [PATCH 19/27] MNTM Settings: Add Skip Sliding Animations option for Lockscreen (#436) * Add Fast Lock/Unlock The cover animation takes time and blocks input while animating, so add options to skip the animations. * Combine options into "Skip Sliding Animation" * Update changelog * Wording --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + .../momentum_app_scene_interface_lockscreen.c | 19 +++++++++++ .../desktop/views/desktop_view_locked.c | 33 ++++++++++++++----- lib/momentum/settings.c | 2 ++ lib/momentum/settings.h | 1 + 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c1b25ba3..ea86dd408 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - UL: Nero Radio static parse and display more data (by @xMasterX) - UL: Marantec protocol implement CRC verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) - UL: Keeloq Comunello add manually support (by @xMasterX) +- MNTM Settings: Add Skip Sliding Animations option for Lockscreen (by @aaronjamt) ### Updated: - Apps: diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_lockscreen.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_lockscreen.c index 5ab144cfb..0c95b4e40 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_lockscreen.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_lockscreen.c @@ -110,6 +110,15 @@ static void app->save_settings = true; } +static void + momentum_app_scene_interface_lockscreen_lockscreen_skip_animation_changed(VariableItem* item) { + MomentumApp* app = variable_item_get_context(item); + bool value = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, value ? "ON" : "OFF"); + momentum_settings.lockscreen_skip_animation = value; + app->save_settings = true; +} + void momentum_app_scene_interface_lockscreen_on_enter(void* context) { MomentumApp* app = context; VariableItemList* var_item_list = app->var_item_list; @@ -220,6 +229,16 @@ void momentum_app_scene_interface_lockscreen_on_enter(void* context) { variable_item_set_current_value_text( item, momentum_settings.lockscreen_transparent ? "ON" : "OFF"); + item = variable_item_list_add( + var_item_list, + "Skip Sliding Animation", + 2, + momentum_app_scene_interface_lockscreen_lockscreen_skip_animation_changed, + app); + variable_item_set_current_value_index(item, momentum_settings.lockscreen_skip_animation); + variable_item_set_current_value_text( + item, momentum_settings.lockscreen_skip_animation ? "ON" : "OFF"); + variable_item_list_set_enter_callback( var_item_list, momentum_app_scene_interface_lockscreen_var_item_list_callback, app); diff --git a/applications/services/desktop/views/desktop_view_locked.c b/applications/services/desktop/views/desktop_view_locked.c index 7281423d4..f21934485 100644 --- a/applications/services/desktop/views/desktop_view_locked.c +++ b/applications/services/desktop/views/desktop_view_locked.c @@ -288,10 +288,17 @@ void desktop_view_locked_free(DesktopViewLocked* locked_view) { void desktop_view_locked_close_cover(DesktopViewLocked* locked_view) { DesktopViewLockedModel* model = view_get_model(locked_view->view); furi_assert(model->view_state == DesktopViewLockedStateLocked); - model->view_state = DesktopViewLockedStateCoverClosing; - model->cover_offset = COVER_OFFSET_START; - view_commit_model(locked_view->view, true); - furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS); + + if(momentum_settings.lockscreen_skip_animation) { + locked_view->callback(DesktopLockedEventCoversClosed, locked_view->context); + model->cover_offset = COVER_OFFSET_END; + view_commit_model(locked_view->view, true); + } else { + model->view_state = DesktopViewLockedStateCoverClosing; + model->cover_offset = COVER_OFFSET_START; + view_commit_model(locked_view->view, true); + furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS); + } } void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) { @@ -305,11 +312,19 @@ void desktop_view_locked_lock(DesktopViewLocked* locked_view, bool pin_locked) { void desktop_view_locked_unlock(DesktopViewLocked* locked_view) { locked_view->lock_count = 0; DesktopViewLockedModel* model = view_get_model(locked_view->view); - model->view_state = DesktopViewLockedStateCoverOpening; - model->cover_offset = COVER_OFFSET_END; - model->pin_locked = false; - view_commit_model(locked_view->view, true); - furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS); + + if(momentum_settings.lockscreen_skip_animation) { + model->view_state = DesktopViewLockedStateUnlocked; + model->cover_offset = COVER_OFFSET_START; + model->pin_locked = false; + view_commit_model(locked_view->view, true); + } else { + model->view_state = DesktopViewLockedStateCoverOpening; + model->cover_offset = COVER_OFFSET_END; + model->pin_locked = false; + view_commit_model(locked_view->view, true); + furi_timer_start(locked_view->timer, COVER_MOVING_INTERVAL_MS); + } } bool desktop_view_locked_is_locked_hint_visible(DesktopViewLocked* locked_view) { diff --git a/lib/momentum/settings.c b/lib/momentum/settings.c index c3ad95aa4..c2030aac1 100644 --- a/lib/momentum/settings.c +++ b/lib/momentum/settings.c @@ -23,6 +23,7 @@ MomentumSettings momentum_settings = { .lockscreen_statusbar = true, // ON .lockscreen_prompt = true, // ON .lockscreen_transparent = false, // OFF + .lockscreen_skip_animation = false, // OFF .battery_icon = BatteryIconBarPercent, // Bar % .status_icons = true, // ON .bar_borders = true, // ON @@ -96,6 +97,7 @@ static const struct { {setting_bool(lockscreen_statusbar)}, {setting_bool(lockscreen_prompt)}, {setting_bool(lockscreen_transparent)}, + {setting_bool(lockscreen_skip_animation)}, {setting_enum(battery_icon, BatteryIconCount)}, {setting_bool(status_icons)}, {setting_bool(bar_borders)}, diff --git a/lib/momentum/settings.h b/lib/momentum/settings.h index 4c202c26d..87a259d09 100644 --- a/lib/momentum/settings.h +++ b/lib/momentum/settings.h @@ -80,6 +80,7 @@ typedef struct { bool lockscreen_statusbar; bool lockscreen_prompt; bool lockscreen_transparent; + bool lockscreen_skip_animation; BatteryIcon battery_icon; bool status_icons; bool bar_borders; From ffafb6ce69cce81cbe0be0a7effec6f03843ba3a Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sun, 20 Jul 2025 01:48:45 +0100 Subject: [PATCH 20/27] BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439) * [BLE Remote] Add Rename Option Adds an option to rename the advertised Bluetooth device. Closes #410. * Fix formatting * Revert changes to firmware Copies some of the firmware code to modify it, rather than directly modifying it in the firmware. * Fix compile error for USB transport * Similar concept for BadKB too * Save to setting file, polish the edges a bit * Fix LSP warning * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + applications/main/bad_usb/application.fam | 2 +- .../main/bad_usb/helpers/bad_usb_hid.c | 4 +- .../main/bad_usb/helpers/bad_usb_hid.h | 4 +- .../bad_usb/helpers/ble_hid_ext_profile.c | 43 ++ .../bad_usb/helpers/ble_hid_ext_profile.h | 17 + .../main/bad_usb/helpers/ble_hid_profile.c | 429 ------------------ .../main/bad_usb/helpers/ble_hid_profile.h | 109 ----- .../main/bad_usb/helpers/ble_hid_service.c | 325 ------------- .../main/bad_usb/helpers/ble_hid_service.h | 31 -- .../main/bad_usb/helpers/ducky_script.c | 2 +- .../bad_usb/scenes/bad_usb_scene_config.c | 2 +- .../hid_app/helpers/ble_hid_ext_profile.c | 36 ++ .../hid_app/helpers/ble_hid_ext_profile.h | 14 + applications/system/hid_app/hid.c | 70 ++- applications/system/hid_app/hid.h | 6 +- .../system/hid_app/scenes/hid_scene_config.h | 1 + .../system/hid_app/scenes/hid_scene_rename.c | 82 ++++ .../system/hid_app/scenes/hid_scene_start.c | 9 + applications/system/hid_app/views.h | 1 + .../system/hid_app/views/hid_music_macos.h | 3 +- 21 files changed, 287 insertions(+), 904 deletions(-) create mode 100644 applications/main/bad_usb/helpers/ble_hid_ext_profile.c create mode 100644 applications/main/bad_usb/helpers/ble_hid_ext_profile.h delete mode 100644 applications/main/bad_usb/helpers/ble_hid_profile.c delete mode 100644 applications/main/bad_usb/helpers/ble_hid_profile.h delete mode 100644 applications/main/bad_usb/helpers/ble_hid_service.c delete mode 100644 applications/main/bad_usb/helpers/ble_hid_service.h create mode 100644 applications/system/hid_app/helpers/ble_hid_ext_profile.c create mode 100644 applications/system/hid_app/helpers/ble_hid_ext_profile.h create mode 100644 applications/system/hid_app/scenes/hid_scene_rename.c diff --git a/CHANGELOG.md b/CHANGELOG.md index ea86dd408..8e7ab7946 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - UL: Nero Radio static parse and display more data (by @xMasterX) - UL: Marantec protocol implement CRC verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) - UL: Keeloq Comunello add manually support (by @xMasterX) +- BT Remote: Add Rename Option, simplify Bad KB BLE profile (by @aaronjamt & @WillyJL) - MNTM Settings: Add Skip Sliding Animations option for Lockscreen (by @aaronjamt) ### Updated: diff --git a/applications/main/bad_usb/application.fam b/applications/main/bad_usb/application.fam index 737e6b52b..96dad84e1 100644 --- a/applications/main/bad_usb/application.fam +++ b/applications/main/bad_usb/application.fam @@ -9,7 +9,7 @@ App( icon="A_BadUsb_14", order=70, resources="resources", - fap_libs=["assets"], + fap_libs=["assets", "ble_profile"], fap_icon="icon.png", fap_category="Tools", ) diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.c b/applications/main/bad_usb/helpers/bad_usb_hid.c index 3243c9944..b8ea28e1e 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.c +++ b/applications/main/bad_usb/helpers/bad_usb_hid.c @@ -1,5 +1,5 @@ #include "bad_usb_hid.h" -#include "ble_hid_profile.h" +#include "ble_hid_ext_profile.h" #include #include #include @@ -173,7 +173,7 @@ void* hid_ble_init(BadUsbHidConfig* hid_cfg) { bt_keys_storage_set_storage_path(ble_hid->bt, APP_DATA_PATH(HID_BT_KEYS_STORAGE_NAME)); hid_ble_adjust_config(hid_cfg); - ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid, &hid_cfg->ble); + ble_hid->profile = bt_profile_start(ble_hid->bt, ble_profile_hid_ext, &hid_cfg->ble); furi_check(ble_hid->profile); furi_hal_bt_start_advertising(); diff --git a/applications/main/bad_usb/helpers/bad_usb_hid.h b/applications/main/bad_usb/helpers/bad_usb_hid.h index 8749bdc3b..8a96ad7f3 100644 --- a/applications/main/bad_usb/helpers/bad_usb_hid.h +++ b/applications/main/bad_usb/helpers/bad_usb_hid.h @@ -7,7 +7,7 @@ extern "C" { #include #include -#include "ble_hid_profile.h" +#include "ble_hid_ext_profile.h" typedef enum { BadUsbHidInterfaceUsb, @@ -16,7 +16,7 @@ typedef enum { } BadUsbHidInterface; typedef struct { - BleProfileHidParams ble; + BleProfileHidExtParams ble; FuriHalUsbHidConfig usb; } BadUsbHidConfig; diff --git a/applications/main/bad_usb/helpers/ble_hid_ext_profile.c b/applications/main/bad_usb/helpers/ble_hid_ext_profile.c new file mode 100644 index 000000000..f77d6ba13 --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_ext_profile.c @@ -0,0 +1,43 @@ +#include "ble_hid_ext_profile.h" + +#include + +static FuriHalBleProfileBase* ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + return ble_profile_hid->start(NULL); +} + +static void ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) { + ble_profile_hid->stop(profile); +} + +static void + ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + furi_check(config); + furi_check(profile_params); + BleProfileHidExtParams* hid_ext_profile_params = profile_params; + + // Setup config with basic profile + ble_profile_hid->get_gap_config(config, NULL); + + // Set MAC address + memcpy(config->mac_address, hid_ext_profile_params->mac, sizeof(config->mac_address)); + + // Set advertise name (skip first byte which is the ADV type) + strlcpy(config->adv_name + 1, hid_ext_profile_params->name, sizeof(config->adv_name) - 1); + + // Set bonding mode + config->bonding_mode = hid_ext_profile_params->bonding; + + // Set pairing method + config->pairing_method = hid_ext_profile_params->pairing; +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_ext_start, + .stop = ble_profile_hid_ext_stop, + .get_gap_config = ble_profile_hid_ext_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid_ext = &profile_callbacks; diff --git a/applications/main/bad_usb/helpers/ble_hid_ext_profile.h b/applications/main/bad_usb/helpers/ble_hid_ext_profile.h new file mode 100644 index 000000000..cc52f0ee7 --- /dev/null +++ b/applications/main/bad_usb/helpers/ble_hid_ext_profile.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */ + uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */ + bool bonding; /**< Save paired devices */ + GapPairing pairing; /**< Pairing security method */ +} BleProfileHidExtParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid_ext; diff --git a/applications/main/bad_usb/helpers/ble_hid_profile.c b/applications/main/bad_usb/helpers/ble_hid_profile.c deleted file mode 100644 index a4f32159e..000000000 --- a/applications/main/bad_usb/helpers/ble_hid_profile.c +++ /dev/null @@ -1,429 +0,0 @@ -#include "ble_hid_profile.h" - -// Based on - -#include -#include -#include -#include "ble_hid_service.h" - -#include -#include -#include - -#define HID_INFO_BASE_USB_SPECIFICATION (0x0101) -#define HID_INFO_COUNTRY_CODE (0x00) -#define BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK (0x01) -#define BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK (0x02) - -#define BLE_PROFILE_HID_KB_MAX_KEYS (6) -#define BLE_PROFILE_CONSUMER_MAX_KEYS (1) - -// Report ids cant be 0 -enum HidReportId { - ReportIdKeyboard = 1, - ReportIdMouse = 2, - ReportIdConsumer = 3, -}; -// Report numbers corresponded to the report id with an offset of 1 -enum HidInputNumber { - ReportNumberKeyboard = 0, - ReportNumberMouse = 1, - ReportNumberConsumer = 2, -}; - -typedef struct { - uint8_t mods; - uint8_t reserved; - uint8_t key[BLE_PROFILE_HID_KB_MAX_KEYS]; -} FURI_PACKED FuriHalBtHidKbReport; - -typedef struct { - uint8_t btn; - int8_t x; - int8_t y; - int8_t wheel; -} FURI_PACKED FuriHalBtHidMouseReport; - -typedef struct { - uint16_t key[BLE_PROFILE_CONSUMER_MAX_KEYS]; -} FURI_PACKED FuriHalBtHidConsumerReport; - -// keyboard+mouse+consumer hid report -static const uint8_t ble_profile_hid_report_map_data[] = { - // Keyboard Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_KEYBOARD), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdKeyboard), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(HID_KEYBOARD_L_CTRL), - HID_USAGE_MAXIMUM(HID_KEYBOARD_R_GUI), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(8), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(1), - HID_REPORT_SIZE(8), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_LED), - HID_REPORT_COUNT(8), - HID_REPORT_SIZE(1), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(8), - HID_OUTPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_COUNT(BLE_PROFILE_HID_KB_MAX_KEYS), - HID_REPORT_SIZE(8), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(101), - HID_USAGE_PAGE(HID_DESKTOP_KEYPAD), - HID_USAGE_MINIMUM(0), - HID_USAGE_MAXIMUM(101), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, - // Mouse Report - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_MOUSE), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_USAGE(HID_DESKTOP_POINTER), - HID_COLLECTION(HID_PHYSICAL_COLLECTION), - HID_REPORT_ID(ReportIdMouse), - HID_USAGE_PAGE(HID_PAGE_BUTTON), - HID_USAGE_MINIMUM(1), - HID_USAGE_MAXIMUM(3), - HID_LOGICAL_MINIMUM(0), - HID_LOGICAL_MAXIMUM(1), - HID_REPORT_COUNT(3), - HID_REPORT_SIZE(1), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_REPORT_SIZE(1), - HID_REPORT_COUNT(5), - HID_INPUT(HID_IOF_CONSTANT | HID_IOF_VARIABLE | HID_IOF_ABSOLUTE), - HID_USAGE_PAGE(HID_PAGE_DESKTOP), - HID_USAGE(HID_DESKTOP_X), - HID_USAGE(HID_DESKTOP_Y), - HID_USAGE(HID_DESKTOP_WHEEL), - HID_LOGICAL_MINIMUM(-127), - HID_LOGICAL_MAXIMUM(127), - HID_REPORT_SIZE(8), - HID_REPORT_COUNT(3), - HID_INPUT(HID_IOF_DATA | HID_IOF_VARIABLE | HID_IOF_RELATIVE), - HID_END_COLLECTION, - HID_END_COLLECTION, - // Consumer Report - HID_USAGE_PAGE(HID_PAGE_CONSUMER), - HID_USAGE(HID_CONSUMER_CONTROL), - HID_COLLECTION(HID_APPLICATION_COLLECTION), - HID_REPORT_ID(ReportIdConsumer), - HID_LOGICAL_MINIMUM(0), - HID_RI_LOGICAL_MAXIMUM(16, 0x3FF), - HID_USAGE_MINIMUM(0), - HID_RI_USAGE_MAXIMUM(16, 0x3FF), - HID_REPORT_COUNT(BLE_PROFILE_CONSUMER_MAX_KEYS), - HID_REPORT_SIZE(16), - HID_INPUT(HID_IOF_DATA | HID_IOF_ARRAY | HID_IOF_ABSOLUTE), - HID_END_COLLECTION, -}; - -typedef struct { - FuriHalBleProfileBase base; - - FuriHalBtHidKbReport* kb_report; - FuriHalBtHidMouseReport* mouse_report; - FuriHalBtHidConsumerReport* consumer_report; - - BleServiceBattery* battery_svc; - BleServiceDevInfo* dev_info_svc; - BleServiceHid* hid_svc; -} BleProfileHid; -_Static_assert(offsetof(BleProfileHid, base) == 0, "Wrong layout"); - -static FuriHalBleProfileBase* ble_profile_hid_start(FuriHalBleProfileParams profile_params) { - UNUSED(profile_params); - - BleProfileHid* profile = malloc(sizeof(BleProfileHid)); - - profile->base.config = ble_profile_hid; - - profile->battery_svc = ble_svc_battery_start(true); - profile->dev_info_svc = ble_svc_dev_info_start(); - profile->hid_svc = ble_svc_hid_start(); - - // Configure HID Keyboard - profile->kb_report = malloc(sizeof(FuriHalBtHidKbReport)); - profile->mouse_report = malloc(sizeof(FuriHalBtHidMouseReport)); - profile->consumer_report = malloc(sizeof(FuriHalBtHidConsumerReport)); - - // Configure Report Map characteristic - ble_svc_hid_update_report_map( - profile->hid_svc, - ble_profile_hid_report_map_data, - sizeof(ble_profile_hid_report_map_data)); - // Configure HID Information characteristic - uint8_t hid_info_val[4] = { - HID_INFO_BASE_USB_SPECIFICATION & 0x00ff, - (HID_INFO_BASE_USB_SPECIFICATION & 0xff00) >> 8, - HID_INFO_COUNTRY_CODE, - BLE_PROFILE_HID_INFO_FLAG_REMOTE_WAKE_MSK | - BLE_PROFILE_HID_INFO_FLAG_NORMALLY_CONNECTABLE_MSK, - }; - ble_svc_hid_update_info(profile->hid_svc, hid_info_val); - - return &profile->base; -} - -static void ble_profile_hid_stop(FuriHalBleProfileBase* profile) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - ble_svc_battery_stop(hid_profile->battery_svc); - ble_svc_dev_info_stop(hid_profile->dev_info_svc); - ble_svc_hid_stop(hid_profile->hid_svc); - - free(hid_profile->kb_report); - free(hid_profile->mouse_report); - free(hid_profile->consumer_report); -} - -bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; - for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { - if(kb_report->key[i] == 0) { - kb_report->key[i] = button & 0xFF; - break; - } - } - kb_report->mods |= (button >> 8); - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberKeyboard, - (uint8_t*)kb_report, - sizeof(FuriHalBtHidKbReport)); -} - -bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - - FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; - for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { - if(kb_report->key[i] == (button & 0xFF)) { - kb_report->key[i] = 0; - break; - } - } - kb_report->mods &= ~(button >> 8); - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberKeyboard, - (uint8_t*)kb_report, - sizeof(FuriHalBtHidKbReport)); -} - -bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidKbReport* kb_report = hid_profile->kb_report; - for(uint8_t i = 0; i < BLE_PROFILE_HID_KB_MAX_KEYS; i++) { - kb_report->key[i] = 0; - } - kb_report->mods = 0; - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberKeyboard, - (uint8_t*)kb_report, - sizeof(FuriHalBtHidKbReport)); -} - -bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; - for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == 0) { - consumer_report->key[i] = button; - break; - } - } - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberConsumer, - (uint8_t*)consumer_report, - sizeof(FuriHalBtHidConsumerReport)); -} - -bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; - for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 - if(consumer_report->key[i] == button) { - consumer_report->key[i] = 0; - break; - } - } - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberConsumer, - (uint8_t*)consumer_report, - sizeof(FuriHalBtHidConsumerReport)); -} - -bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidConsumerReport* consumer_report = hid_profile->consumer_report; - for(uint8_t i = 0; i < BLE_PROFILE_CONSUMER_MAX_KEYS; i++) { //-V1008 - consumer_report->key[i] = 0; - } - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberConsumer, - (uint8_t*)consumer_report, - sizeof(FuriHalBtHidConsumerReport)); -} - -bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; - mouse_report->x = dx; - mouse_report->y = dy; - bool state = ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberMouse, - (uint8_t*)mouse_report, - sizeof(FuriHalBtHidMouseReport)); - mouse_report->x = 0; - mouse_report->y = 0; - return state; -} - -bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; - mouse_report->btn |= button; - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberMouse, - (uint8_t*)mouse_report, - sizeof(FuriHalBtHidMouseReport)); -} - -bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; - mouse_report->btn &= ~button; - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberMouse, - (uint8_t*)mouse_report, - sizeof(FuriHalBtHidMouseReport)); -} - -bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; - mouse_report->btn = 0; - return ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberMouse, - (uint8_t*)mouse_report, - sizeof(FuriHalBtHidMouseReport)); -} - -bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta) { - furi_check(profile); - furi_check(profile->config == ble_profile_hid); - - BleProfileHid* hid_profile = (BleProfileHid*)profile; - FuriHalBtHidMouseReport* mouse_report = hid_profile->mouse_report; - mouse_report->wheel = delta; - bool state = ble_svc_hid_update_input_report( - hid_profile->hid_svc, - ReportNumberMouse, - (uint8_t*)mouse_report, - sizeof(FuriHalBtHidMouseReport)); - mouse_report->wheel = 0; - return state; -} - -// AN5289: 4.7, in order to use flash controller interval must be at least 25ms + advertisement, which is 30 ms -// Since we don't use flash controller anymore interval can be lowered to 7.5ms -#define CONNECTION_INTERVAL_MIN (0x0006) -// Up to 45 ms -#define CONNECTION_INTERVAL_MAX (0x24) - -static GapConfig template_config = { - .adv_service = - { - .UUID_Type = UUID_TYPE_16, - .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, - }, - .appearance_char = GAP_APPEARANCE_KEYBOARD, - .bonding_mode = true, - .pairing_method = GapPairingPinCodeVerifyYesNo, - .conn_param = - { - .conn_int_min = CONNECTION_INTERVAL_MIN, - .conn_int_max = CONNECTION_INTERVAL_MAX, - .slave_latency = 0, - .supervisor_timeout = 0, - }, -}; - -static void ble_profile_hid_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { - furi_check(profile_params); - BleProfileHidParams* hid_profile_params = profile_params; - - furi_check(config); - memcpy(config, &template_config, sizeof(GapConfig)); - - // Set MAC address - memcpy(config->mac_address, hid_profile_params->mac, sizeof(config->mac_address)); - - // Set advertise name - config->adv_name[0] = furi_hal_version_get_ble_local_device_name_ptr()[0]; - strlcpy(config->adv_name + 1, hid_profile_params->name, sizeof(config->adv_name) - 1); - - // Set bonding mode - config->bonding_mode = hid_profile_params->bonding; - - // Set pairing method - config->pairing_method = hid_profile_params->pairing; -} - -static const FuriHalBleProfileTemplate profile_callbacks = { - .start = ble_profile_hid_start, - .stop = ble_profile_hid_stop, - .get_gap_config = ble_profile_hid_get_config, -}; - -const FuriHalBleProfileTemplate* ble_profile_hid = &profile_callbacks; diff --git a/applications/main/bad_usb/helpers/ble_hid_profile.h b/applications/main/bad_usb/helpers/ble_hid_profile.h deleted file mode 100644 index 2302aa581..000000000 --- a/applications/main/bad_usb/helpers/ble_hid_profile.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -// Based on - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * Optional arguments to pass along with profile template as - * FuriHalBleProfileParams for tuning profile behavior - **/ -typedef struct { - char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */ - uint8_t mac[GAP_MAC_ADDR_SIZE]; /**< Full device address */ - bool bonding; /**< Save paired devices */ - GapPairing pairing; /**< Pairing security method */ -} BleProfileHidParams; - -/** Hid Keyboard Profile descriptor */ -extern const FuriHalBleProfileTemplate* ble_profile_hid; - -/** Press keyboard button - * - * @param profile profile instance - * @param button button code from HID specification - * - * @return true on success - */ -bool ble_profile_hid_kb_press(FuriHalBleProfileBase* profile, uint16_t button); - -/** Release keyboard button - * - * @param profile profile instance - * @param button button code from HID specification - * - * @return true on success - */ -bool ble_profile_hid_kb_release(FuriHalBleProfileBase* profile, uint16_t button); - -/** Release all keyboard buttons - * - * @param profile profile instance - * @return true on success - */ -bool ble_profile_hid_kb_release_all(FuriHalBleProfileBase* profile); - -/** Set the following consumer key to pressed state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_consumer_key_press(FuriHalBleProfileBase* profile, uint16_t button); - -/** Set the following consumer key to released state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_consumer_key_release(FuriHalBleProfileBase* profile, uint16_t button); - -/** Set consumer key to released state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_consumer_key_release_all(FuriHalBleProfileBase* profile); - -/** Set mouse movement and send HID report - * - * @param profile profile instance - * @param dx x coordinate delta - * @param dy y coordinate delta - */ -bool ble_profile_hid_mouse_move(FuriHalBleProfileBase* profile, int8_t dx, int8_t dy); - -/** Set mouse button to pressed state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_mouse_press(FuriHalBleProfileBase* profile, uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_mouse_release(FuriHalBleProfileBase* profile, uint8_t button); - -/** Set mouse button to released state and send HID report - * - * @param profile profile instance - * @param button key code - */ -bool ble_profile_hid_mouse_release_all(FuriHalBleProfileBase* profile); - -/** Set mouse wheel position and send HID report - * - * @param profile profile instance - * @param delta number of scroll steps - */ -bool ble_profile_hid_mouse_scroll(FuriHalBleProfileBase* profile, int8_t delta); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/bad_usb/helpers/ble_hid_service.c b/applications/main/bad_usb/helpers/ble_hid_service.c deleted file mode 100644 index b546368dd..000000000 --- a/applications/main/bad_usb/helpers/ble_hid_service.c +++ /dev/null @@ -1,325 +0,0 @@ -#include "ble_hid_service.h" - -// Based on - -#include "app_common.h" // IWYU pragma: keep -#include -#include -#include - -#include -#include - -#define TAG "BleHid" - -#define BLE_SVC_HID_REPORT_MAP_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_MAX_LEN (255) -#define BLE_SVC_HID_REPORT_REF_LEN (2) -#define BLE_SVC_HID_INFO_LEN (4) -#define BLE_SVC_HID_CONTROL_POINT_LEN (1) - -#define BLE_SVC_HID_INPUT_REPORT_COUNT (3) -#define BLE_SVC_HID_OUTPUT_REPORT_COUNT (0) -#define BLE_SVC_HID_FEATURE_REPORT_COUNT (0) -#define BLE_SVC_HID_REPORT_COUNT \ - (BLE_SVC_HID_INPUT_REPORT_COUNT + BLE_SVC_HID_OUTPUT_REPORT_COUNT + \ - BLE_SVC_HID_FEATURE_REPORT_COUNT) - -typedef enum { - HidSvcGattCharacteristicProtocolMode = 0, - HidSvcGattCharacteristicReportMap, - HidSvcGattCharacteristicInfo, - HidSvcGattCharacteristicCtrlPoint, - HidSvcGattCharacteristicCount, -} HidSvcGattCharacteristicId; - -typedef struct { - uint8_t report_idx; - uint8_t report_type; -} HidSvcReportId; - -static_assert(sizeof(HidSvcReportId) == sizeof(uint16_t), "HidSvcReportId must be 2 bytes"); - -static const Service_UUID_t ble_svc_hid_uuid = { - .Service_UUID_16 = HUMAN_INTERFACE_DEVICE_SERVICE_UUID, -}; - -static bool ble_svc_hid_char_desc_data_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const HidSvcReportId* report_id = context; - *data_len = sizeof(HidSvcReportId); - if(data) { - *data = (const uint8_t*)report_id; - } - return false; -} - -typedef struct { - const void* data_ptr; - uint16_t data_len; -} HidSvcDataWrapper; - -static bool ble_svc_hid_report_data_callback( - const void* context, - const uint8_t** data, - uint16_t* data_len) { - const HidSvcDataWrapper* report_data = context; - if(data) { - *data = report_data->data_ptr; - *data_len = report_data->data_len; - } else { - *data_len = BLE_SVC_HID_REPORT_MAP_MAX_LEN; - } - return false; -} - -static const BleGattCharacteristicParams ble_svc_hid_chars[HidSvcGattCharacteristicCount] = { - [HidSvcGattCharacteristicProtocolMode] = - {.name = "Protocol Mode", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = 1, - .uuid.Char_UUID_16 = PROTOCOL_MODE_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_WRITE_WITHOUT_RESP, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [HidSvcGattCharacteristicReportMap] = - {.name = "Report Map", - .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = ble_svc_hid_report_data_callback, - .data.callback.context = NULL, - .uuid.Char_UUID_16 = REPORT_MAP_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_VARIABLE}, - [HidSvcGattCharacteristicInfo] = - {.name = "HID Information", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = BLE_SVC_HID_INFO_LEN, - .data.fixed.ptr = NULL, - .uuid.Char_UUID_16 = HID_INFORMATION_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, - [HidSvcGattCharacteristicCtrlPoint] = - {.name = "HID Control Point", - .data_prop_type = FlipperGattCharacteristicDataFixed, - .data.fixed.length = BLE_SVC_HID_CONTROL_POINT_LEN, - .uuid.Char_UUID_16 = HID_CONTROL_POINT_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_WRITE_WITHOUT_RESP, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_NOTIFY_ATTRIBUTE_WRITE, - .is_variable = CHAR_VALUE_LEN_CONSTANT}, -}; - -static const BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr_template = { - .uuid_type = UUID_TYPE_16, - .uuid.Char_UUID_16 = REPORT_REFERENCE_DESCRIPTOR_UUID, - .max_length = BLE_SVC_HID_REPORT_REF_LEN, - .data_callback.fn = ble_svc_hid_char_desc_data_callback, - .security_permissions = ATTR_PERMISSION_NONE, - .access_permissions = ATTR_ACCESS_READ_WRITE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_CONSTANT, -}; - -static const BleGattCharacteristicParams ble_svc_hid_report_template = { - .name = "Report", - .data_prop_type = FlipperGattCharacteristicDataCallback, - .data.callback.fn = ble_svc_hid_report_data_callback, - .data.callback.context = NULL, - .uuid.Char_UUID_16 = REPORT_CHAR_UUID, - .uuid_type = UUID_TYPE_16, - .char_properties = CHAR_PROP_READ | CHAR_PROP_NOTIFY, - .security_permissions = ATTR_PERMISSION_NONE, - .gatt_evt_mask = GATT_DONT_NOTIFY_EVENTS, - .is_variable = CHAR_VALUE_LEN_VARIABLE, -}; - -struct BleServiceHid { - uint16_t svc_handle; - BleGattCharacteristicInstance chars[HidSvcGattCharacteristicCount]; - BleGattCharacteristicInstance input_report_chars[BLE_SVC_HID_INPUT_REPORT_COUNT]; - BleGattCharacteristicInstance output_report_chars[BLE_SVC_HID_OUTPUT_REPORT_COUNT]; - BleGattCharacteristicInstance feature_report_chars[BLE_SVC_HID_FEATURE_REPORT_COUNT]; - GapSvcEventHandler* event_handler; -}; - -static BleEventAckStatus ble_svc_hid_event_handler(void* event, void* context) { - UNUSED(context); - - BleEventAckStatus ret = BleEventNotAck; - hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data); - evt_blecore_aci* blecore_evt = (evt_blecore_aci*)event_pckt->data; - // aci_gatt_attribute_modified_event_rp0* attribute_modified; - - if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) { - if(blecore_evt->ecode == ACI_GATT_ATTRIBUTE_MODIFIED_VSEVT_CODE) { - // Process modification events - ret = BleEventAckFlowEnable; - } else if(blecore_evt->ecode == ACI_GATT_SERVER_CONFIRMATION_VSEVT_CODE) { - // Process notification confirmation - ret = BleEventAckFlowEnable; - } - } - return ret; -} - -BleServiceHid* ble_svc_hid_start(void) { - BleServiceHid* hid_svc = malloc(sizeof(BleServiceHid)); - - // Register event handler - hid_svc->event_handler = - ble_event_dispatcher_register_svc_handler(ble_svc_hid_event_handler, hid_svc); - /** - * Add Human Interface Device Service - */ - if(!ble_gatt_service_add( - UUID_TYPE_16, - &ble_svc_hid_uuid, - PRIMARY_SERVICE, - 2 + /* protocol mode */ - (4 * BLE_SVC_HID_INPUT_REPORT_COUNT) + (3 * BLE_SVC_HID_OUTPUT_REPORT_COUNT) + - (3 * BLE_SVC_HID_FEATURE_REPORT_COUNT) + 1 + 2 + 2 + - 2, /* Service + Report Map + HID Information + HID Control Point */ - &hid_svc->svc_handle)) { - free(hid_svc); - return NULL; - } - - // Maintain previously defined characteristic order - ble_gatt_characteristic_init( - hid_svc->svc_handle, - &ble_svc_hid_chars[HidSvcGattCharacteristicProtocolMode], - &hid_svc->chars[HidSvcGattCharacteristicProtocolMode]); - - uint8_t protocol_mode = 1; - ble_gatt_characteristic_update( - hid_svc->svc_handle, - &hid_svc->chars[HidSvcGattCharacteristicProtocolMode], - &protocol_mode); - - // reports - BleGattCharacteristicDescriptorParams ble_svc_hid_char_descr; - BleGattCharacteristicParams report_char; - HidSvcReportId report_id; - - memcpy( - &ble_svc_hid_char_descr, &ble_svc_hid_char_descr_template, sizeof(ble_svc_hid_char_descr)); - memcpy(&report_char, &ble_svc_hid_report_template, sizeof(report_char)); - - ble_svc_hid_char_descr.data_callback.context = &report_id; - report_char.descriptor_params = &ble_svc_hid_char_descr; - - typedef struct { - uint8_t report_type; - uint8_t report_count; - BleGattCharacteristicInstance* chars; - } HidSvcReportCharProps; - - HidSvcReportCharProps hid_report_chars[] = { - {0x01, BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {0x02, BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {0x03, BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, - }; - - for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); - report_type_idx++) { - report_id.report_type = hid_report_chars[report_type_idx].report_type; - for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; - report_idx++) { - report_id.report_idx = report_idx + 1; - ble_gatt_characteristic_init( - hid_svc->svc_handle, - &report_char, - &hid_report_chars[report_type_idx].chars[report_idx]); - } - } - - // Setup remaining characteristics - for(size_t i = HidSvcGattCharacteristicReportMap; i < HidSvcGattCharacteristicCount; i++) { - ble_gatt_characteristic_init( - hid_svc->svc_handle, &ble_svc_hid_chars[i], &hid_svc->chars[i]); - } - - return hid_svc; -} - -bool ble_svc_hid_update_report_map(BleServiceHid* hid_svc, const uint8_t* data, uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - - HidSvcDataWrapper report_data = { - .data_ptr = data, - .data_len = len, - }; - return ble_gatt_characteristic_update( - hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicReportMap], &report_data); -} - -bool ble_svc_hid_update_input_report( - BleServiceHid* hid_svc, - uint8_t input_report_num, - uint8_t* data, - uint16_t len) { - furi_assert(data); - furi_assert(hid_svc); - furi_assert(input_report_num < BLE_SVC_HID_INPUT_REPORT_COUNT); - - HidSvcDataWrapper report_data = { - .data_ptr = data, - .data_len = len, - }; - - return ble_gatt_characteristic_update( - hid_svc->svc_handle, &hid_svc->input_report_chars[input_report_num], &report_data); -} - -bool ble_svc_hid_update_info(BleServiceHid* hid_svc, uint8_t* data) { - furi_assert(data); - furi_assert(hid_svc); - - return ble_gatt_characteristic_update( - hid_svc->svc_handle, &hid_svc->chars[HidSvcGattCharacteristicInfo], &data); -} - -void ble_svc_hid_stop(BleServiceHid* hid_svc) { - furi_assert(hid_svc); - ble_event_dispatcher_unregister_svc_handler(hid_svc->event_handler); - // Delete characteristics - for(size_t i = 0; i < HidSvcGattCharacteristicCount; i++) { - ble_gatt_characteristic_delete(hid_svc->svc_handle, &hid_svc->chars[i]); - } - - typedef struct { - uint8_t report_count; - BleGattCharacteristicInstance* chars; - } HidSvcReportCharProps; - - HidSvcReportCharProps hid_report_chars[] = { - {BLE_SVC_HID_INPUT_REPORT_COUNT, hid_svc->input_report_chars}, - {BLE_SVC_HID_OUTPUT_REPORT_COUNT, hid_svc->output_report_chars}, - {BLE_SVC_HID_FEATURE_REPORT_COUNT, hid_svc->feature_report_chars}, - }; - - for(size_t report_type_idx = 0; report_type_idx < COUNT_OF(hid_report_chars); - report_type_idx++) { - for(size_t report_idx = 0; report_idx < hid_report_chars[report_type_idx].report_count; - report_idx++) { - ble_gatt_characteristic_delete( - hid_svc->svc_handle, &hid_report_chars[report_type_idx].chars[report_idx]); - } - } - - // Delete service - ble_gatt_service_delete(hid_svc->svc_handle); - free(hid_svc); -} diff --git a/applications/main/bad_usb/helpers/ble_hid_service.h b/applications/main/bad_usb/helpers/ble_hid_service.h deleted file mode 100644 index e1ac3b0be..000000000 --- a/applications/main/bad_usb/helpers/ble_hid_service.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -// Based on - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct BleServiceHid BleServiceHid; - -BleServiceHid* ble_svc_hid_start(void); - -void ble_svc_hid_stop(BleServiceHid* service); - -bool ble_svc_hid_update_report_map(BleServiceHid* service, const uint8_t* data, uint16_t len); - -bool ble_svc_hid_update_input_report( - BleServiceHid* service, - uint8_t input_report_num, - uint8_t* data, - uint16_t len); - -// Expects data to be of length BLE_SVC_HID_INFO_LEN (4 bytes) -bool ble_svc_hid_update_info(BleServiceHid* service, uint8_t* data); - -#ifdef __cplusplus -} -#endif diff --git a/applications/main/bad_usb/helpers/ducky_script.c b/applications/main/bad_usb/helpers/ducky_script.c index 0c75c1166..7bd91c89b 100644 --- a/applications/main/bad_usb/helpers/ducky_script.c +++ b/applications/main/bad_usb/helpers/ducky_script.c @@ -257,7 +257,7 @@ static bool ducky_set_usb_id(BadUsbScript* bad_usb, const char* line) { } static bool ducky_set_ble_id(BadUsbScript* bad_usb, const char* line) { - BleProfileHidParams* ble_hid_cfg = &bad_usb->hid_cfg->ble; + BleProfileHidExtParams* ble_hid_cfg = &bad_usb->hid_cfg->ble; size_t line_len = strlen(line); size_t mac_len = sizeof(ble_hid_cfg->mac) * 3; // 2 hex chars + separator per byte diff --git a/applications/main/bad_usb/scenes/bad_usb_scene_config.c b/applications/main/bad_usb/scenes/bad_usb_scene_config.c index 5c59fe6b9..adf989dfc 100644 --- a/applications/main/bad_usb/scenes/bad_usb_scene_config.c +++ b/applications/main/bad_usb/scenes/bad_usb_scene_config.c @@ -84,7 +84,7 @@ static void draw_menu(BadUsbApp* bad_usb) { item, bad_usb->interface == BadUsbHidInterfaceBle ? "BLE" : "USB"); if(bad_usb->interface == BadUsbHidInterfaceBle) { - BleProfileHidParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble; + BleProfileHidExtParams* ble_hid_cfg = &bad_usb->script_hid_cfg.ble; item = variable_item_list_add( var_item_list, diff --git a/applications/system/hid_app/helpers/ble_hid_ext_profile.c b/applications/system/hid_app/helpers/ble_hid_ext_profile.c new file mode 100644 index 000000000..f1858318e --- /dev/null +++ b/applications/system/hid_app/helpers/ble_hid_ext_profile.c @@ -0,0 +1,36 @@ +#include "ble_hid_ext_profile.h" + +#include + +static FuriHalBleProfileBase* ble_profile_hid_ext_start(FuriHalBleProfileParams profile_params) { + UNUSED(profile_params); + + return ble_profile_hid->start(NULL); +} + +static void ble_profile_hid_ext_stop(FuriHalBleProfileBase* profile) { + ble_profile_hid->stop(profile); +} + +static void + ble_profile_hid_ext_get_config(GapConfig* config, FuriHalBleProfileParams profile_params) { + furi_check(config); + furi_check(profile_params); + BleProfileHidExtParams* hid_ext_profile_params = profile_params; + + // Setup config with basic profile + ble_profile_hid->get_gap_config(config, NULL); + + if(hid_ext_profile_params->name[0] != '\0') { + // Set advertise name (skip first byte which is the ADV type) + strlcpy(config->adv_name + 1, hid_ext_profile_params->name, sizeof(config->adv_name) - 1); + } +} + +static const FuriHalBleProfileTemplate profile_callbacks = { + .start = ble_profile_hid_ext_start, + .stop = ble_profile_hid_ext_stop, + .get_gap_config = ble_profile_hid_ext_get_config, +}; + +const FuriHalBleProfileTemplate* ble_profile_hid_ext = &profile_callbacks; diff --git a/applications/system/hid_app/helpers/ble_hid_ext_profile.h b/applications/system/hid_app/helpers/ble_hid_ext_profile.h new file mode 100644 index 000000000..fc0bdbb89 --- /dev/null +++ b/applications/system/hid_app/helpers/ble_hid_ext_profile.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +/** + * Optional arguments to pass along with profile template as + * FuriHalBleProfileParams for tuning profile behavior + **/ +typedef struct { + char name[FURI_HAL_BT_ADV_NAME_LENGTH]; /**< Full device name */ +} BleProfileHidExtParams; + +/** Hid Keyboard Profile descriptor */ +extern const FuriHalBleProfileTemplate* ble_profile_hid_ext; diff --git a/applications/system/hid_app/hid.c b/applications/system/hid_app/hid.c index e297e0738..00c4ee666 100644 --- a/applications/system/hid_app/hid.c +++ b/applications/system/hid_app/hid.c @@ -4,9 +4,14 @@ #include "views.h" #include #include +#include #define TAG "HidApp" +#define HID_BT_CFG_PATH APP_DATA_PATH(".bt_hid.cfg") +#define HID_BT_CFG_FILE_TYPE "Flipper BT Remote Settings File" +#define HID_BT_CFG_VERSION 1 + bool hid_custom_event_callback(void* context, uint32_t event) { furi_assert(context); Hid* app = context; @@ -33,6 +38,60 @@ void bt_hid_remove_pairing(Hid* app) { furi_hal_bt_start_advertising(); } +static void bt_hid_load_cfg(Hid* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + bool loaded = false; + + FuriString* temp_str = furi_string_alloc(); + uint32_t temp_uint = 0; + + do { + if(!flipper_format_file_open_existing(fff, HID_BT_CFG_PATH)) break; + + if(!flipper_format_read_header(fff, temp_str, &temp_uint)) break; + if((strcmp(furi_string_get_cstr(temp_str), HID_BT_CFG_FILE_TYPE) != 0) || + (temp_uint != HID_BT_CFG_VERSION)) + break; + + if(flipper_format_read_string(fff, "name", temp_str)) { + strlcpy( + app->ble_hid_cfg.name, + furi_string_get_cstr(temp_str), + sizeof(app->ble_hid_cfg.name)); + } else { + flipper_format_rewind(fff); + } + + loaded = true; + } while(0); + + furi_string_free(temp_str); + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); + + if(!loaded) { + app->ble_hid_cfg.name[0] = '\0'; + } +} + +void bt_hid_save_cfg(Hid* app) { + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperFormat* fff = flipper_format_file_alloc(storage); + + if(flipper_format_file_open_always(fff, HID_BT_CFG_PATH)) { + do { + if(!flipper_format_write_header_cstr(fff, HID_BT_CFG_FILE_TYPE, HID_BT_CFG_VERSION)) + break; + if(!flipper_format_write_string_cstr(fff, "name", app->ble_hid_cfg.name)) break; + } while(0); + } + + flipper_format_free(fff); + furi_record_close(RECORD_STORAGE); +} + static void bt_hid_connection_status_changed_callback(BtStatus status, void* context) { furi_assert(context); Hid* hid = context; @@ -89,6 +148,11 @@ Hid* hid_alloc() { app->dialog = dialog_ex_alloc(); view_dispatcher_add_view(app->view_dispatcher, HidViewDialog, dialog_ex_get_view(app->dialog)); + // Text input + app->text_input = text_input_alloc(); + view_dispatcher_add_view( + app->view_dispatcher, HidViewTextInput, text_input_get_view(app->text_input)); + // Popup view app->popup = popup_alloc(); view_dispatcher_add_view(app->view_dispatcher, HidViewPopup, popup_get_view(app->popup)); @@ -177,6 +241,8 @@ void hid_free(Hid* app) { submenu_free(app->submenu); view_dispatcher_remove_view(app->view_dispatcher, HidViewDialog); dialog_ex_free(app->dialog); + view_dispatcher_remove_view(app->view_dispatcher, HidViewTextInput); + text_input_free(app->text_input); view_dispatcher_remove_view(app->view_dispatcher, HidViewPopup); popup_free(app->popup); view_dispatcher_remove_view(app->view_dispatcher, HidViewKeynote); @@ -266,7 +332,9 @@ int32_t hid_ble_app(void* p) { furi_record_close(RECORD_STORAGE); - app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid, NULL); + bt_hid_load_cfg(app); + + app->ble_hid_profile = bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg); furi_check(app->ble_hid_profile); diff --git a/applications/system/hid_app/hid.h b/applications/system/hid_app/hid.h index ac565217a..e314c005c 100644 --- a/applications/system/hid_app/hid.h +++ b/applications/system/hid_app/hid.h @@ -5,7 +5,7 @@ #include #include -#include +#include "helpers/ble_hid_ext_profile.h" #include #include @@ -18,6 +18,7 @@ #include #include #include +#include #include "views/hid_keynote.h" #include "views/hid_keyboard.h" #include "views/hid_numpad.h" @@ -40,6 +41,7 @@ typedef struct Hid Hid; struct Hid { FuriHalBleProfileBase* ble_hid_profile; + BleProfileHidExtParams ble_hid_cfg; Bt* bt; Gui* gui; NotificationApp* notifications; @@ -47,6 +49,7 @@ struct Hid { SceneManager* scene_manager; Submenu* submenu; DialogEx* dialog; + TextInput* text_input; Popup* popup; HidKeynote* hid_keynote; HidKeyboard* hid_keyboard; @@ -64,6 +67,7 @@ struct Hid { }; void bt_hid_remove_pairing(Hid* app); +void bt_hid_save_cfg(Hid* app); void hid_hal_keyboard_press(Hid* instance, uint16_t event); void hid_hal_keyboard_release(Hid* instance, uint16_t event); diff --git a/applications/system/hid_app/scenes/hid_scene_config.h b/applications/system/hid_app/scenes/hid_scene_config.h index d18b15558..2064c65b1 100644 --- a/applications/system/hid_app/scenes/hid_scene_config.h +++ b/applications/system/hid_app/scenes/hid_scene_config.h @@ -1,3 +1,4 @@ ADD_SCENE(hid, start, Start) ADD_SCENE(hid, main, Main) ADD_SCENE(hid, unpair, Unpair) +ADD_SCENE(hid, rename, Rename) diff --git a/applications/system/hid_app/scenes/hid_scene_rename.c b/applications/system/hid_app/scenes/hid_scene_rename.c new file mode 100644 index 000000000..983c7d927 --- /dev/null +++ b/applications/system/hid_app/scenes/hid_scene_rename.c @@ -0,0 +1,82 @@ +#include "../hid.h" +#include "../views.h" +#include "hid_icons.h" + +enum HidSceneRenameEvent { + HidSceneRenameEventTextInput, + HidSceneRenameEventPopup, +}; + +static void hid_scene_rename_text_input_callback(void* context) { + Hid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventTextInput); +} + +void hid_scene_rename_popup_callback(void* context) { + Hid* app = context; + + view_dispatcher_send_custom_event(app->view_dispatcher, HidSceneRenameEventPopup); +} + +void hid_scene_rename_on_enter(void* context) { + Hid* app = context; + + // Rename text input view + text_input_reset(app->text_input); + text_input_set_result_callback( + app->text_input, + hid_scene_rename_text_input_callback, + app, + app->ble_hid_cfg.name, + sizeof(app->ble_hid_cfg.name), + true); + text_input_set_header_text(app->text_input, "Bluetooth Name"); + + // Rename success popup view + popup_set_icon(app->popup, 48, 6, &I_DolphinDone_80x58); + popup_set_header(app->popup, "Done", 14, 15, AlignLeft, AlignTop); + popup_set_timeout(app->popup, 1500); + popup_set_context(app->popup, app); + popup_set_callback(app->popup, hid_scene_rename_popup_callback); + popup_enable_timeout(app->popup); + + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewTextInput); +} + +bool hid_scene_rename_on_event(void* context, SceneManagerEvent event) { + Hid* app = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + consumed = true; + if(event.event == HidSceneRenameEventTextInput) { +#ifdef HID_TRANSPORT_BLE + furi_hal_bt_stop_advertising(); + + app->ble_hid_profile = + bt_profile_start(app->bt, ble_profile_hid_ext, &app->ble_hid_cfg); + furi_check(app->ble_hid_profile); + + furi_hal_bt_start_advertising(); +#endif + + bt_hid_save_cfg(app); + + // Show popup + view_dispatcher_switch_to_view(app->view_dispatcher, HidViewPopup); + + } else if(event.event == HidSceneRenameEventPopup) { + scene_manager_previous_scene(app->scene_manager); + } + } + + return consumed; +} + +void hid_scene_rename_on_exit(void* context) { + Hid* app = context; + + text_input_reset(app->text_input); + popup_reset(app->popup); +} diff --git a/applications/system/hid_app/scenes/hid_scene_start.c b/applications/system/hid_app/scenes/hid_scene_start.c index 61d340eec..167b967e6 100644 --- a/applications/system/hid_app/scenes/hid_scene_start.c +++ b/applications/system/hid_app/scenes/hid_scene_start.c @@ -15,6 +15,7 @@ enum HidSubmenuIndex { HidSubmenuIndexMouseJiggler, HidSubmenuIndexMouseJigglerStealth, HidSubmenuIndexPushToTalk, + HidSubmenuIndexRename, HidSubmenuIndexRemovePairing, }; @@ -81,6 +82,12 @@ void hid_scene_start_on_enter(void* context) { hid_scene_start_submenu_callback, app); #ifdef HID_TRANSPORT_BLE + submenu_add_item( + app->submenu, + "Bluetooth Remote Name", + HidSubmenuIndexRename, + hid_scene_start_submenu_callback, + app); submenu_add_item( app->submenu, "Bluetooth Unpairing", @@ -101,6 +108,8 @@ bool hid_scene_start_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == HidSubmenuIndexRemovePairing) { scene_manager_next_scene(app->scene_manager, HidSceneUnpair); + } else if(event.event == HidSubmenuIndexRename) { + scene_manager_next_scene(app->scene_manager, HidSceneRename); } else { HidView view_id; diff --git a/applications/system/hid_app/views.h b/applications/system/hid_app/views.h index 606a48daf..e27d3ecf0 100644 --- a/applications/system/hid_app/views.h +++ b/applications/system/hid_app/views.h @@ -16,4 +16,5 @@ typedef enum { HidViewPushToTalkHelp, HidViewDialog, HidViewPopup, + HidViewTextInput, } HidView; diff --git a/applications/system/hid_app/views/hid_music_macos.h b/applications/system/hid_app/views/hid_music_macos.h index 9deac32eb..f185276f3 100644 --- a/applications/system/hid_app/views/hid_music_macos.h +++ b/applications/system/hid_app/views/hid_music_macos.h @@ -2,9 +2,10 @@ #include +typedef struct Hid Hid; typedef struct HidMusicMacos HidMusicMacos; -HidMusicMacos* hid_music_macos_alloc(); +HidMusicMacos* hid_music_macos_alloc(Hid* hid); void hid_music_macos_free(HidMusicMacos* hid_music_macos); From 4f014a630adc46c426e36451fa118b86450177dc Mon Sep 17 00:00:00 2001 From: Aaron Tulino Date: Sun, 20 Jul 2025 01:50:16 +0100 Subject: [PATCH 21/27] Desktop: Fix lock screen hang (#438) * Fix lock screen hang See #438 * Desktop: Dont delay screen off at boot * Update changelog * Revert "Desktop: Dont delay screen off at boot" This reverts commit ff43264258a75ffb367212b201b6be592c8d3ba9. * Update changelog * Format --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + applications/services/desktop/scenes/desktop_scene_locked.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e7ab7946..6322f6fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ ### Fixed: - Bad KB: Fix modifier keys with HOLD/RELEASE commands (by @WillyJL) +- Desktop: Fix lock screen hang (#438 by @aaronjamt) ### Removed: - Nothing diff --git a/applications/services/desktop/scenes/desktop_scene_locked.c b/applications/services/desktop/scenes/desktop_scene_locked.c index c15ca4e10..bf9b027d6 100644 --- a/applications/services/desktop/scenes/desktop_scene_locked.c +++ b/applications/services/desktop/scenes/desktop_scene_locked.c @@ -101,8 +101,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) { break; case DesktopLockedEventUpdate: if(desktop_view_locked_is_locked_hint_visible(desktop->locked_view)) { - notification_message( - desktop->notification, &sequence_display_backlight_off_delay_1000); + notification_message(desktop->notification, &sequence_display_backlight_off); } desktop_view_locked_update(desktop->locked_view); consumed = true; From 2bcb89a5507c887318537c07e440969cd66a57ec Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 20 Jul 2025 02:42:16 +0100 Subject: [PATCH 22/27] Update apps - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) - Combo Cracker: Add tutorial (by @TAxelAnderson) - Flipper Blackhat: Add Deauth Broadcast command (by @o7-machinehum) - NFC Playlist: Refactor playlist worker, new settings layout, loop setting, controls to move between items (by @acegoal07) - Sentry Safe: New interface, settings & help page (by @H4ckd4ddy) --- CHANGELOG.md | 6 +++++- applications/external | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6322f6fbf..fc0d10efa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,13 @@ ### Updated: - Apps: - - Combo Cracker: Allow press and hold to change values (by @TAxelAnderson) + - Asteroids: Bugfixes, title screen, Drone Buddy power-up (by @SimplyMinimal) + - Combo Cracker: Allow press and hold to change values, add tutorial (by @TAxelAnderson) - FlipDownloader: Added a new option to download GitHub repositories (by @jblanked) + - Flipper Blackhat: Add Deauth Broadcast command (by @o7-machinehum) - KeyCopier: Added Weiser WR3 key format (by @lightos) + - NFC Playlist: Refactor playlist worker, new settings layout, loop setting, controls to move between items (by @acegoal07) + - Sentry Safe: New interface, settings & help page (by @H4ckd4ddy) - Sub-GHz: - UL: Add 868.46 MHz to default subghz freqs list (by @xMasterX) - UL: Reduce less popular freqs in default hopper preset, make it faster (by @xMasterX) diff --git a/applications/external b/applications/external index c8336cab2..65ff6249a 160000 --- a/applications/external +++ b/applications/external @@ -1 +1 @@ -Subproject commit c8336cab2f3d69b6bc77064f99263061661fce1c +Subproject commit 65ff6249aaff9db45eddf814f54939d251c0b4a3 From ef17b42a5ea2982033aad5afe5862c16d71c28bc Mon Sep 17 00:00:00 2001 From: Lucifer Date: Sun, 20 Jul 2025 08:43:45 +0700 Subject: [PATCH 23/27] NFC: Fix incorrect Saflok year formula (#433) * Fix incorrect Saflok year formula * Update changelog * Format? --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + applications/main/nfc/plugins/supported_cards/saflok.c | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc0d10efa..bc811d87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ ### Fixed: - Bad KB: Fix modifier keys with HOLD/RELEASE commands (by @WillyJL) - Desktop: Fix lock screen hang (#438 by @aaronjamt) +- NFC: Fix incorrect Saflok year formula (#433 by @Eltrick) ### Removed: - Nothing diff --git a/applications/main/nfc/plugins/supported_cards/saflok.c b/applications/main/nfc/plugins/supported_cards/saflok.c index 9940b7cb1..9cab61ee5 100644 --- a/applications/main/nfc/plugins/supported_cards/saflok.c +++ b/applications/main/nfc/plugins/supported_cards/saflok.c @@ -347,8 +347,8 @@ bool saflok_parse(const NfcDevice* device, FuriString* parsed_data) { uint8_t interval_minute = decodedBA[10] & 0x3F; // Bytes 11-13: Creation date since 1980 Jan 1st - uint16_t creation_year = (((decodedBA[11] & 0xF0) >> 4) + SAFLOK_YEAR_OFFSET) | - creation_year_bits; + uint16_t creation_year = + (creation_year_bits | ((decodedBA[11] & 0xF0) >> 4)) + SAFLOK_YEAR_OFFSET; uint8_t creation_month = decodedBA[11] & 0x0F; uint8_t creation_day = (decodedBA[12] >> 3) & 0x1F; uint8_t creation_hour = ((decodedBA[12] & 0x07) << 2) | (decodedBA[13] >> 6); From ec922aa5afd528cd0d43e2f5f3735edc0db9c097 Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Sat, 19 Jul 2025 19:44:14 -0600 Subject: [PATCH 24/27] RFID: Add additional procotols supported by EM4305 chipset (#434) * Add additional procotols supported by EM4305 chipset * Add support for GProxII (inverted EM4305 BIPHASE) * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + lib/lfrfid/protocols/protocol_electra.c | 3 +-- lib/lfrfid/protocols/protocol_fdx_b.c | 15 ++++++++++++ lib/lfrfid/protocols/protocol_gproxii.c | 14 +++++++++++ lib/lfrfid/protocols/protocol_jablotron.c | 13 ++++++++++ lib/lfrfid/protocols/protocol_securakey.c | 29 +++++++++++++++++++++++ 6 files changed, 73 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc811d87c..55145e21c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - UL: Nero Radio static parse and display more data (by @xMasterX) - UL: Marantec protocol implement CRC verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) - UL: Keeloq Comunello add manually support (by @xMasterX) +- RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek) - BT Remote: Add Rename Option, simplify Bad KB BLE profile (by @aaronjamt & @WillyJL) - MNTM Settings: Add Skip Sliding Animations option for Lockscreen (by @aaronjamt) diff --git a/lib/lfrfid/protocols/protocol_electra.c b/lib/lfrfid/protocols/protocol_electra.c index 50d82c8f1..d107cd6e3 100644 --- a/lib/lfrfid/protocols/protocol_electra.c +++ b/lib/lfrfid/protocols/protocol_electra.c @@ -406,8 +406,7 @@ bool protocol_electra_write_data(ProtocolElectra* protocol, void* data) { request->t5577.block[4] = protocol->encoded_epilogue & 0xFFFFFFFF; request->t5577.blocks_to_write = 5; result = true; - } - if(request->write_type == LFRFIDWriteTypeEM4305) { + } else if(request->write_type == LFRFIDWriteTypeEM4305) { request->em4305.word[4] = (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(64) | (8 << EM4x05_MAXBLOCK_SHIFT)); uint64_t encoded_data_reversed = 0; diff --git a/lib/lfrfid/protocols/protocol_fdx_b.c b/lib/lfrfid/protocols/protocol_fdx_b.c index 3de2b661b..df37a7e0f 100644 --- a/lib/lfrfid/protocols/protocol_fdx_b.c +++ b/lib/lfrfid/protocols/protocol_fdx_b.c @@ -369,6 +369,21 @@ bool protocol_fdx_b_write_data(ProtocolFDXB* protocol, void* data) { request->t5577.block[4] = bit_lib_get_bits_32(protocol->encoded_data, 96, 32); request->t5577.blocks_to_write = 5; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(32) | (8 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[4] = {0}; + for(uint8_t i = 0; i < 128; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, (127 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[3]; + request->em4305.word[6] = encoded_data_reversed[2]; + request->em4305.word[7] = encoded_data_reversed[1]; + request->em4305.word[8] = encoded_data_reversed[0]; + request->em4305.mask = 0x1F0; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_gproxii.c b/lib/lfrfid/protocols/protocol_gproxii.c index 341d092e9..18d20ee75 100644 --- a/lib/lfrfid/protocols/protocol_gproxii.c +++ b/lib/lfrfid/protocols/protocol_gproxii.c @@ -287,6 +287,20 @@ bool protocol_gproxii_write_data(ProtocolGProxII* protocol, void* data) { request->t5577.block[3] = bit_lib_get_bits_32(protocol->data, 64, 32); request->t5577.blocks_to_write = 4; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(64) | (7 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[3] = {0}; + for(uint8_t i = 0; i < 96; i++) { + encoded_data_reversed[i / 32] = (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->data, (95 - i)) & 1); + encoded_data_reversed[i / 32] ^= 1; // Invert to make DIPHASE/BIPHASE. + } + request->em4305.word[5] = encoded_data_reversed[2]; + request->em4305.word[6] = encoded_data_reversed[1]; + request->em4305.word[7] = encoded_data_reversed[0]; + request->em4305.mask = 0xF0; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_jablotron.c b/lib/lfrfid/protocols/protocol_jablotron.c index 643bb1be6..8e3628a3b 100644 --- a/lib/lfrfid/protocols/protocol_jablotron.c +++ b/lib/lfrfid/protocols/protocol_jablotron.c @@ -182,6 +182,19 @@ bool protocol_jablotron_write_data(ProtocolJablotron* protocol, void* data) { request->t5577.block[2] = bit_lib_get_bits_32(protocol->encoded_data, 32, 32); request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_BIPHASE | EM4x05_SET_BITRATE(64) | (6 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[2] = {0}; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->encoded_data, (63 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[1]; + request->em4305.word[6] = encoded_data_reversed[0]; + request->em4305.mask = 0x70; + result = true; } return result; } diff --git a/lib/lfrfid/protocols/protocol_securakey.c b/lib/lfrfid/protocols/protocol_securakey.c index 947b68e72..9c9fb8ab1 100644 --- a/lib/lfrfid/protocols/protocol_securakey.c +++ b/lib/lfrfid/protocols/protocol_securakey.c @@ -328,6 +328,20 @@ bool protocol_securakey_write_data(ProtocolSecurakey* protocol, void* data) { request->t5577.block[2] = bit_lib_get_bits_32(protocol->RKKTH_encoded_data, 32, 32); request->t5577.blocks_to_write = 3; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(40) | // requires 330pF card + (6 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[2] = {0}; + for(uint8_t i = 0; i < 64; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->RKKTH_encoded_data, (63 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[1]; + request->em4305.word[6] = encoded_data_reversed[0]; + request->em4305.mask = 0x70; + result = true; } } else { if(request->write_type == LFRFIDWriteTypeT5577) { @@ -340,6 +354,21 @@ bool protocol_securakey_write_data(ProtocolSecurakey* protocol, void* data) { request->t5577.block[3] = bit_lib_get_bits_32(protocol->RKKT_encoded_data, 64, 32); request->t5577.blocks_to_write = 4; result = true; + } else if(request->write_type == LFRFIDWriteTypeEM4305) { + request->em4305.word[4] = + (EM4x05_MODULATION_MANCHESTER | EM4x05_SET_BITRATE(40) | // requires 330pF card + (7 << EM4x05_MAXBLOCK_SHIFT)); + uint32_t encoded_data_reversed[3] = {0}; + for(uint8_t i = 0; i < 96; i++) { + encoded_data_reversed[i / 32] = + (encoded_data_reversed[i / 32] << 1) | + (bit_lib_get_bit(protocol->RKKT_encoded_data, (95 - i)) & 1); + } + request->em4305.word[5] = encoded_data_reversed[2]; + request->em4305.word[6] = encoded_data_reversed[1]; + request->em4305.word[7] = encoded_data_reversed[0]; + request->em4305.mask = 0xF0; + result = true; } } return result; From c64f184a15afb26ab72269c3d03f97ac8bb849af Mon Sep 17 00:00:00 2001 From: WillyJL Date: Sun, 20 Jul 2025 02:46:36 +0100 Subject: [PATCH 25/27] Fix changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55145e21c..f04a65ac3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ - UL: Marantec protocol implement CRC verification display and add manually support (by @xMasterX & @li0ard, original code by @Skorpionm) - UL: Keeloq Comunello add manually support (by @xMasterX) - RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek) -- BT Remote: Add Rename Option, simplify Bad KB BLE profile (by @aaronjamt & @WillyJL) -- MNTM Settings: Add Skip Sliding Animations option for Lockscreen (by @aaronjamt) +- BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439 by @aaronjamt & @WillyJL) +- MNTM Settings: Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt) ### Updated: - Apps: From 3193361e4921be2385b0c16584b8909927bf1048 Mon Sep 17 00:00:00 2001 From: Alexander Bays Date: Tue, 22 Jul 2025 01:49:29 +0000 Subject: [PATCH 26/27] Input Settings: Add Vibro Trigger option (#429) * Add `Vibro Trigger` to input settings Adds vibro trigger type to input settings to allow vibration on only press/release, or both. * Didn't format, apparently * Use a mask for smaller code, migrate setting to new version * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 1 + applications/services/input/input.c | 5 +- applications/services/input/input_settings.c | 19 +++++++- applications/services/input/input_settings.h | 1 + .../input_settings_app/input_settings_app.c | 46 +++++++++++++++++-- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f04a65ac3..7d66074f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek) - BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439 by @aaronjamt & @WillyJL) - MNTM Settings: Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt) +- Input Settings: Add Vibro Trigger option (#429 by @956MB) ### Updated: - Apps: diff --git a/applications/services/input/input.c b/applications/services/input/input.c index fb6eb7a25..8d1267a90 100644 --- a/applications/services/input/input.c +++ b/applications/services/input/input.c @@ -156,13 +156,14 @@ int32_t input_srv(void* p) { event.type = pin_states[i].state ? InputTypePress : InputTypeRelease; furi_pubsub_publish(event_pubsub, &event); // vibro signal if user setup vibro touch level in Settings-Input. - if(settings->vibro_touch_level) { + if(settings->vibro_touch_level && + ((1 << event.type) & settings->vibro_touch_trigger_mask)) { //delay 1 ticks for compatibility with rgb_backlight_mod furi_delay_tick(1); furi_hal_vibro_on(true); furi_delay_tick(settings->vibro_touch_level); furi_hal_vibro_on(false); - }; + } } } diff --git a/applications/services/input/input_settings.c b/applications/services/input/input_settings.c index d48990847..df2928fad 100644 --- a/applications/services/input/input_settings.c +++ b/applications/services/input/input_settings.c @@ -1,15 +1,19 @@ #include "input_settings.h" #include "input_settings_filename.h" +#include "input.h" #include #include #define TAG "InputSettings" -#define INPUT_SETTINGS_VER (1) // version nnumber +#define INPUT_SETTINGS_VER (2) // version number #define INPUT_SETTINGS_MAGIC (0x29) +#define INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT \ + ((1 << InputTypePress) | (1 << InputTypeRelease)) + void input_settings_load(InputSettings* settings) { furi_assert(settings); @@ -21,6 +25,18 @@ void input_settings_load(InputSettings* settings) { uint8_t version; if(!saved_struct_get_metadata(INPUT_SETTINGS_PATH, NULL, &version, NULL)) break; + if(version == 1) { + struct { + uint8_t vibro_touch_level; + } v1; + if(!saved_struct_load(INPUT_SETTINGS_PATH, &v1, sizeof(v1), INPUT_SETTINGS_MAGIC, 1)) + break; + settings->vibro_touch_level = v1.vibro_touch_level; + settings->vibro_touch_trigger_mask = INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT; + success = true; + break; + } + // if config actual version - load it directly if(version == INPUT_SETTINGS_VER) { success = saved_struct_load( @@ -37,6 +53,7 @@ void input_settings_load(InputSettings* settings) { if(!success) { FURI_LOG_W(TAG, "Failed to load file, using defaults"); memset(settings, 0, sizeof(InputSettings)); + settings->vibro_touch_trigger_mask = INPUT_SETTINGS_VIBRO_TOUCH_TRIGGER_MASK_DEFAULT; // input_settings_save(settings); } } diff --git a/applications/services/input/input_settings.h b/applications/services/input/input_settings.h index 861c15ac4..2dce6586a 100644 --- a/applications/services/input/input_settings.h +++ b/applications/services/input/input_settings.h @@ -6,6 +6,7 @@ typedef struct { uint8_t vibro_touch_level; + uint8_t vibro_touch_trigger_mask; } InputSettings; void input_settings_load(InputSettings* settings); diff --git a/applications/settings/input_settings_app/input_settings_app.c b/applications/settings/input_settings_app/input_settings_app.c index 4f3e101da..035a84e79 100644 --- a/applications/settings/input_settings_app/input_settings_app.c +++ b/applications/settings/input_settings_app/input_settings_app.c @@ -3,7 +3,9 @@ #define TAG "InputSettingsApp" -#define VIBRO_TOUCH_LEVEL_COUNT 10 +#define VIBRO_TOUCH_LEVEL_COUNT 10 +#define VIBRO_TOUCH_TRIGGER_MASK_COUNT 3 + // vibro touch human readable levels const char* const vibro_touch_level_text[VIBRO_TOUCH_LEVEL_COUNT] = { "OFF", @@ -20,21 +22,45 @@ const char* const vibro_touch_level_text[VIBRO_TOUCH_LEVEL_COUNT] = { // vibro touch levels tick valies delay const uint32_t vibro_touch_level_value[VIBRO_TOUCH_LEVEL_COUNT] = {0, 13, 16, 19, 21, 24, 27, 30, 33, 36}; +// vibro touch trigger mask human readable values +const char* const vibro_touch_trigger_mask_text[VIBRO_TOUCH_TRIGGER_MASK_COUNT] = { + "Press", + "Release", + "Both", +}; +// vibro touch trigger mask values +const uint32_t vibro_touch_trigger_mask_value[VIBRO_TOUCH_TRIGGER_MASK_COUNT] = { + (1 << InputTypePress), + (1 << InputTypeRelease), + (1 << InputTypePress) | (1 << InputTypeRelease), +}; static void input_settings_vibro_touch_level_changed(VariableItem* item) { uint8_t index = variable_item_get_current_value_index(item); variable_item_set_current_value_text(item, vibro_touch_level_text[index]); - //change settings to selected InputSettingsApp* app = variable_item_get_context(item); app->settings->vibro_touch_level = vibro_touch_level_value[index]; - // use RECORD for acces to input service instance and set settings + // use RECORD for access to input service instance and set settings InputSettings* service_settings = furi_record_open(RECORD_INPUT_SETTINGS); service_settings->vibro_touch_level = vibro_touch_level_value[index]; furi_record_close(RECORD_INPUT_SETTINGS); } +static void input_settings_vibro_touch_trigger_mask_changed(VariableItem* item) { + uint8_t index = variable_item_get_current_value_index(item); + variable_item_set_current_value_text(item, vibro_touch_trigger_mask_text[index]); + + InputSettingsApp* app = variable_item_get_context(item); + app->settings->vibro_touch_trigger_mask = vibro_touch_trigger_mask_value[index]; + + // use RECORD for access to input service instance and set settings + InputSettings* service_settings = furi_record_open(RECORD_INPUT_SETTINGS); + service_settings->vibro_touch_trigger_mask = vibro_touch_trigger_mask_value[index]; + furi_record_close(RECORD_INPUT_SETTINGS); +} + static uint32_t input_settings_app_exit(void* context) { UNUSED(context); return VIEW_NONE; @@ -66,6 +92,20 @@ InputSettingsApp* input_settings_app_alloc(void) { variable_item_set_current_value_index(item, value_index); variable_item_set_current_value_text(item, vibro_touch_level_text[value_index]); + item = variable_item_list_add( + app->variable_item_list, + "Vibro Trigger", + VIBRO_TOUCH_TRIGGER_MASK_COUNT, + input_settings_vibro_touch_trigger_mask_changed, + app); + + value_index = value_index_uint32( + app->settings->vibro_touch_trigger_mask, + vibro_touch_trigger_mask_value, + VIBRO_TOUCH_TRIGGER_MASK_COUNT); + variable_item_set_current_value_index(item, value_index); + variable_item_set_current_value_text(item, vibro_touch_trigger_mask_text[value_index]); + // create and setup view and view dispatcher app->view_dispatcher = view_dispatcher_alloc(); view_dispatcher_set_event_callback_context(app->view_dispatcher, app); From 0e3e1b352b13288dc67b65f1f83d2e04252d9c00 Mon Sep 17 00:00:00 2001 From: Alexander Bays Date: Tue, 22 Jul 2025 01:51:33 +0000 Subject: [PATCH 27/27] Desktop / MNTM Settings: Directories and generic files support for Keybinds / Main Menu (#331) * feat(Desktop): Directories support for keybinds - Adds *RIGHT* button select in the file browser dialogs and changing the `Open File` action to `Open File/Directory` in `Settings > Desktop > Keybinds Setup`. This adds the ability to open to any directory in the Archive app, in addition to the default behavior of opening a file in it's default app. * line order mixup * Main Menu: Allow adding JS files (or any file) - Normal files and directories are now able to be added to then main menu and are run in their appropriate apps. - e.g. .txt files shown in text viewer, .js files are run in the JS Runner app, and folders are navigated to by the Archive app. All similar to the desktop keybinds functionality. - Icons are also assigned appropriately based on the extensions, though more could probably be added to the `loader_menu_get_ext_icon` function. - Also replaced some of the long arduous is_dir checks and just used the `storage_dir_exists` function since its already there and does the same. * should be checking `ext` for NULL * Move select_right at end of structs for binary compatibility apps may blindly reach into these structs so need to keep the basics in same structure for DialogsFileBrowserOptions this is even in public api and after compilation this would be incompatible with other firmwares even without reaching into private structs * Select menu item / folder for directories too * Move api below too * Keep ofw order here too * Refactor starting archive into desktop, less FuriString passing around * Dont leave main menu when launching archive * Simplify/fix a few things * Handle folders in run_with_default_app() * Update App -> Item naming in MNTM settings * Fix build * Explain pressing right * Update changelog --------- Co-authored-by: WillyJL --- CHANGELOG.md | 5 +- applications/main/archive/archive.c | 17 ++++++- .../main/archive/helpers/archive_browser.c | 2 +- .../main/archive/helpers/archive_browser.h | 7 +++ .../main/archive/helpers/archive_files.c | 8 ++- .../archive/scenes/archive_scene_browser.c | 8 +++ applications/main/momentum_app/momentum_app.c | 29 +++++------ .../momentum_app_scene_interface_mainmenu.c | 38 +++++++------- ...omentum_app_scene_interface_mainmenu_add.c | 49 +++++++++++++------ ...entum_app_scene_interface_mainmenu_reset.c | 2 +- applications/services/desktop/desktop.c | 11 +++++ applications/services/desktop/desktop_i.h | 3 ++ .../services/desktop/desktop_keybinds.c | 10 ++-- .../desktop/scenes/desktop_scene_main.c | 9 ++-- applications/services/dialogs/dialogs.c | 1 + applications/services/dialogs/dialogs.h | 3 ++ applications/services/dialogs/dialogs_api.c | 1 + .../services/dialogs/dialogs_message.h | 2 + .../dialogs/dialogs_module_file_browser.c | 1 + .../services/gui/modules/file_browser.c | 37 +++++++++++++- .../services/gui/modules/file_browser.h | 2 + applications/services/loader/loader_menu.c | 22 +++++++-- .../desktop_settings/desktop_settings_app.h | 2 +- ...ktop_settings_scene_keybinds_action_type.c | 16 +++--- targets/f7/api_symbols.csv | 1 + 25 files changed, 207 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d66074f6..dbf7cecb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,10 @@ - UL: Keeloq Comunello add manually support (by @xMasterX) - RFID: Support writing Securakey, Jablotron and FDX-B to EM4305 cards (#434 by @jamisonderek) - BT Remote: Add Rename Option, simplify Bad KB BLE profile (#439 by @aaronjamt & @WillyJL) -- MNTM Settings: Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt) +- MNTM Settings: + - Add Main Menu support for directories and generic files (including JS files) (#331 by @956MB & @WillyJL) + - Add Skip Sliding Animations option for Lockscreen (#436 by @aaronjamt) +- Desktop: Add Keybinds support for directories (#331 by @956MB & @WillyJL) - Input Settings: Add Vibro Trigger option (#429 by @956MB) ### Updated: diff --git a/applications/main/archive/archive.c b/applications/main/archive/archive.c index 640bd3072..5d50ff27a 100644 --- a/applications/main/archive/archive.c +++ b/applications/main/archive/archive.c @@ -1,4 +1,5 @@ #include "archive_i.h" +#include "helpers/archive_browser.h" static bool archive_custom_event_callback(void* context, uint32_t event) { furi_assert(context); @@ -136,11 +137,25 @@ void archive_show_loading_popup(ArchiveApp* context, bool show) { } int32_t archive_app(void* p) { - UNUSED(p); + FuriString* path = (FuriString*)p; ArchiveApp* archive = archive_alloc(); view_dispatcher_attach_to_gui( archive->view_dispatcher, archive->gui, ViewDispatcherTypeFullscreen); + + // If we are sent a path from context, set it in the browser + if(path && !furi_string_empty(path)) { + archive_set_tab(archive->browser, ArchiveTabBrowser); + furi_string_set(archive->browser->path, path); + archive->browser->is_root = false; + archive_file_browser_set_path( + archive->browser, + archive->browser->path, + archive_get_tab_ext(ArchiveTabBrowser), + false, + !momentum_settings.show_hidden_files); + } + scene_manager_next_scene(archive->scene_manager, ArchiveAppSceneBrowser); view_dispatcher_run(archive->view_dispatcher); diff --git a/applications/main/archive/helpers/archive_browser.c b/applications/main/archive/helpers/archive_browser.c index 79a0a3c56..d55180415 100644 --- a/applications/main/archive/helpers/archive_browser.c +++ b/applications/main/archive/helpers/archive_browser.c @@ -116,7 +116,7 @@ static void archive_long_load_cb(void* context) { browser->view, ArchiveBrowserViewModel * model, { model->folder_loading = true; }, true); } -static void archive_file_browser_set_path( +void archive_file_browser_set_path( ArchiveBrowserView* browser, FuriString* path, const char* filter_ext, diff --git a/applications/main/archive/helpers/archive_browser.h b/applications/main/archive/helpers/archive_browser.h index 14c222d6a..9d3c760c6 100644 --- a/applications/main/archive/helpers/archive_browser.h +++ b/applications/main/archive/helpers/archive_browser.h @@ -80,6 +80,12 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) { return type < ArchiveFileTypeUnknown; } +void archive_file_browser_set_path( + ArchiveBrowserView* browser, + FuriString* path, + const char* filter_ext, + bool skip_assets, + bool hide_dot_files); bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx); bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model); void archive_update_offset(ArchiveBrowserView* browser); @@ -104,6 +110,7 @@ void archive_add_file_item(ArchiveBrowserView* browser, bool is_folder, const ch void archive_show_file_menu(ArchiveBrowserView* browser, bool show, bool manage); void archive_favorites_move_mode(ArchiveBrowserView* browser, bool active); +void archive_set_tab(ArchiveBrowserView* browser, ArchiveTabEnum tab); void archive_switch_tab(ArchiveBrowserView* browser, InputKey key); void archive_enter_dir(ArchiveBrowserView* browser, FuriString* name); void archive_leave_dir(ArchiveBrowserView* browser); diff --git a/applications/main/archive/helpers/archive_files.c b/applications/main/archive/helpers/archive_files.c index c71de5d08..c33eba8b9 100644 --- a/applications/main/archive/helpers/archive_files.c +++ b/applications/main/archive/helpers/archive_files.c @@ -15,6 +15,8 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder file->is_app = is_app; if(is_app) { file->type = archive_get_app_filetype(archive_get_app_type(path)); + } else if(is_folder) { + file->type = ArchiveFileTypeFolder; } else { for(size_t i = 0; i < COUNT_OF(known_ext); i++) { if((known_ext[i][0] == '?') || (known_ext[i][0] == '*')) continue; @@ -53,11 +55,7 @@ void archive_set_file_type(ArchiveFile_t* file, const char* path, bool is_folder } } - if(is_folder) { - file->type = ArchiveFileTypeFolder; - } else { - file->type = ArchiveFileTypeUnknown; - } + file->type = ArchiveFileTypeUnknown; } } diff --git a/applications/main/archive/scenes/archive_scene_browser.c b/applications/main/archive/scenes/archive_scene_browser.c index 093132059..b6d51c1a9 100644 --- a/applications/main/archive/scenes/archive_scene_browser.c +++ b/applications/main/archive/scenes/archive_scene_browser.c @@ -6,6 +6,8 @@ #include "../views/archive_browser_view.h" #include "archive/scenes/archive_scene.h" +#include + #define TAG "ArchiveSceneBrowser" #define SCENE_STATE_DEFAULT (0) @@ -182,6 +184,12 @@ static void } } else if(selected->type == ArchiveFileTypeApplication) { loader_start_detached_with_gui_error(loader, furi_string_get_cstr(selected->path), NULL); + } else if(selected->type == ArchiveFileTypeFolder) { + // Folders are handled by archive, so we should only get here with run_with_default_app() outside archive + furi_check(browser == NULL, "What you doin?"); + Desktop* desktop = furi_record_open(RECORD_DESKTOP); + desktop_launch_archive(desktop, furi_string_get_cstr(selected->path)); + furi_record_close(RECORD_DESKTOP); } else { archive_show_file(loader, furi_string_get_cstr(selected->path)); } diff --git a/applications/main/momentum_app/momentum_app.c b/applications/main/momentum_app/momentum_app.c index 9b929ab70..015014ef1 100644 --- a/applications/main/momentum_app/momentum_app.c +++ b/applications/main/momentum_app/momentum_app.c @@ -201,22 +201,23 @@ void momentum_app_load_mainmenu_apps(MomentumApp* app) { if(furi_string_start_with(line, "/")) { if(!flipper_application_load_name_and_icon( line, app->storage, &unused_icon, label)) { - furi_string_reset(label); + const char* end = strrchr(furi_string_get_cstr(line), '/'); + furi_string_set(label, end ? end + 1 : furi_string_get_cstr(line)); } - } else { - furi_string_reset(label); - bool found = false; - for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) { - if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) { - furi_string_set(label, FLIPPER_APPS[i].name); - found = true; - } + momentum_app_push_mainmenu_app(app, label, line); + continue; + } + bool found = false; + for(size_t i = 0; !found && i < FLIPPER_APPS_COUNT; i++) { + if(!strcmp(furi_string_get_cstr(line), FLIPPER_APPS[i].name)) { + furi_string_set(label, FLIPPER_APPS[i].name); + found = true; } - for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { - if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) { - furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name); - found = true; - } + } + for(size_t i = 0; !found && i < FLIPPER_EXTERNAL_APPS_COUNT; i++) { + if(!strcmp(furi_string_get_cstr(line), FLIPPER_EXTERNAL_APPS[i].name)) { + furi_string_set(label, FLIPPER_EXTERNAL_APPS[i].name); + found = true; } } if(furi_string_empty(label)) { diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c index 4743e08b3..fce1f5957 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu.c @@ -3,10 +3,10 @@ enum VarItemListIndex { VarItemListIndexMenuStyle, VarItemListIndexResetMenu, - VarItemListIndexApp, - VarItemListIndexAddApp, - VarItemListIndexMoveApp, - VarItemListIndexRemoveApp, + VarItemListIndexItem, + VarItemListIndexAddItem, + VarItemListIndexMoveItem, + VarItemListIndexRemoveItem, }; void momentum_app_scene_interface_mainmenu_var_item_list_callback(void* context, uint32_t index) { @@ -40,7 +40,7 @@ static void momentum_app_scene_interface_mainmenu_app_changed(VariableItem* item item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index)); size_t count = CharList_size(app->mainmenu_app_labels); char label[20]; - snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count); + snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count); variable_item_set_item_label(item, label); } @@ -61,7 +61,7 @@ static void momentum_app_scene_interface_mainmenu_move_app_changed(VariableItem* CharList_swap_at(app->mainmenu_app_exes, idx, idx - 1); app->mainmenu_app_index--; } - view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveApp); + view_dispatcher_send_custom_event(app->view_dispatcher, VarItemListIndexMoveItem); } variable_item_set_current_value_index(item, 1); } @@ -84,11 +84,11 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) { size_t count = CharList_size(app->mainmenu_app_labels); item = variable_item_list_add( - var_item_list, "App", count, momentum_app_scene_interface_mainmenu_app_changed, app); + var_item_list, "Item", count, momentum_app_scene_interface_mainmenu_app_changed, app); if(count) { app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U); - char label[20]; - snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count); + char label[21]; + snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count); variable_item_set_item_label(item, label); variable_item_set_current_value_text( item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index)); @@ -98,15 +98,15 @@ void momentum_app_scene_interface_mainmenu_on_enter(void* context) { } variable_item_set_current_value_index(item, app->mainmenu_app_index); - variable_item_list_add(var_item_list, "Add App", 0, NULL, app); + variable_item_list_add(var_item_list, "Add Item", 0, NULL, app); item = variable_item_list_add( - var_item_list, "Move App", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app); + var_item_list, "Move Item", 3, momentum_app_scene_interface_mainmenu_move_app_changed, app); variable_item_set_current_value_text(item, ""); variable_item_set_current_value_index(item, 1); variable_item_set_locked(item, count < 2, "Can't move\nwith less\nthan 2 apps!"); - variable_item_list_add(var_item_list, "Remove App", 0, NULL, app); + variable_item_list_add(var_item_list, "Remove Item", 0, NULL, app); variable_item_list_set_enter_callback( var_item_list, momentum_app_scene_interface_mainmenu_var_item_list_callback, app); @@ -133,7 +133,7 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE case VarItemListIndexResetMenu: scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuReset); break; - case VarItemListIndexRemoveApp: + case VarItemListIndexRemoveItem: if(!CharList_size(app->mainmenu_app_labels)) break; if(!CharList_size(app->mainmenu_app_exes)) break; free(*CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index)); @@ -143,27 +143,27 @@ bool momentum_app_scene_interface_mainmenu_on_event(void* context, SceneManagerE CharList_remove_v( app->mainmenu_app_exes, app->mainmenu_app_index, app->mainmenu_app_index + 1); /* fall through */ - case VarItemListIndexMoveApp: { + case VarItemListIndexMoveItem: { app->save_mainmenu_apps = true; size_t count = CharList_size(app->mainmenu_app_labels); - VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexApp); + VariableItem* item = variable_item_list_get(app->var_item_list, VarItemListIndexItem); if(count) { app->mainmenu_app_index = CLAMP(app->mainmenu_app_index, count - 1, 0U); - char label[20]; - snprintf(label, sizeof(label), "App %u/%u", 1 + app->mainmenu_app_index, count); + char label[21]; + snprintf(label, sizeof(label), "Item %u/%u", 1 + app->mainmenu_app_index, count); variable_item_set_item_label(item, label); variable_item_set_current_value_text( item, *CharList_get(app->mainmenu_app_labels, app->mainmenu_app_index)); } else { app->mainmenu_app_index = 0; - variable_item_set_item_label(item, "App"); + variable_item_set_item_label(item, "Item"); variable_item_set_current_value_text(item, "None"); } variable_item_set_current_value_index(item, app->mainmenu_app_index); variable_item_set_values_count(item, count); break; } - case VarItemListIndexAddApp: + case VarItemListIndexAddItem: scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAdd); break; default: diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_add.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_add.c index 2bb63491c..49f765f31 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_add.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_add.c @@ -3,6 +3,7 @@ enum SubmenuIndex { SubmenuIndexMainApp, SubmenuIndexExternalApp, + SubmenuIndexFileDirectory, }; static bool fap_selector_item_callback( @@ -26,28 +27,39 @@ static void case SubmenuIndexMainApp: scene_manager_next_scene(app->scene_manager, MomentumAppSceneInterfaceMainmenuAddMain); break; - case SubmenuIndexExternalApp: { + case SubmenuIndexExternalApp: + case SubmenuIndexFileDirectory: { + const bool is_file_dir = index == SubmenuIndexFileDirectory; const DialogsFileBrowserOptions browser_options = { - .extension = ".fap", + .extension = is_file_dir ? "*" : ".fap", .icon = &I_unknown_10px, .skip_assets = true, - .hide_ext = true, + .hide_ext = !is_file_dir, .item_loader_callback = fap_selector_item_callback, .item_loader_context = app, - .base_path = EXT_PATH("apps"), + .base_path = is_file_dir ? STORAGE_EXT_PATH_PREFIX : EXT_PATH("apps"), + .select_right = is_file_dir, }; - FuriString* temp_path = furi_string_alloc_set_str(EXT_PATH("apps")); + FuriString* temp_path = furi_string_alloc_set_str(browser_options.base_path); + if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) { CharList_push_back(app->mainmenu_app_exes, strdup(furi_string_get_cstr(temp_path))); - Storage* storage = furi_record_open(RECORD_STORAGE); - uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE); - flipper_application_load_name_and_icon(temp_path, storage, &unused_icon, temp_path); - free(unused_icon); - furi_record_close(RECORD_STORAGE); - if(furi_string_start_with_str(temp_path, "[")) { - size_t trim = furi_string_search_str(temp_path, "] ", 1); - if(trim != FURI_STRING_FAILURE) { - furi_string_right(temp_path, trim + 2); + if(is_file_dir) { + const char* path = furi_string_get_cstr(temp_path); + const char* end = strrchr(path, '/'); + furi_string_set_str(temp_path, end ? end + 1 : path); + } else { + Storage* storage = furi_record_open(RECORD_STORAGE); + uint8_t* unused_icon = malloc(FAP_MANIFEST_MAX_ICON_SIZE); + flipper_application_load_name_and_icon( + temp_path, storage, &unused_icon, temp_path); + free(unused_icon); + furi_record_close(RECORD_STORAGE); + if(furi_string_start_with_str(temp_path, "[")) { + size_t trim = furi_string_search_str(temp_path, "] ", 1); + if(trim != FURI_STRING_FAILURE) { + furi_string_right(temp_path, trim + 2); + } } } CharList_push_back(app->mainmenu_app_labels, strdup(furi_string_get_cstr(temp_path))); @@ -68,7 +80,7 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) { MomentumApp* app = context; Submenu* submenu = app->submenu; - submenu_set_header(submenu, "Add Menu App:"); + submenu_set_header(submenu, "Add Menu Item:"); submenu_add_item( submenu, @@ -84,6 +96,13 @@ void momentum_app_scene_interface_mainmenu_add_on_enter(void* context) { momentum_app_scene_interface_mainmenu_add_submenu_callback, app); + submenu_add_item( + submenu, + "File / Directory (right btn)", + SubmenuIndexFileDirectory, + momentum_app_scene_interface_mainmenu_add_submenu_callback, + app); + view_dispatcher_switch_to_view(app->view_dispatcher, MomentumAppViewSubmenu); } diff --git a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_reset.c b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_reset.c index 8fd133ec0..7ad8e70ab 100644 --- a/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_reset.c +++ b/applications/main/momentum_app/scenes/momentum_app_scene_interface_mainmenu_reset.c @@ -12,7 +12,7 @@ void momentum_app_scene_interface_mainmenu_reset_on_enter(void* context) { MomentumApp* app = context; DialogEx* dialog_ex = app->dialog_ex; - dialog_ex_set_header(dialog_ex, "Reset Menu Apps?", 64, 10, AlignCenter, AlignCenter); + dialog_ex_set_header(dialog_ex, "Reset Menu Items?", 64, 10, AlignCenter, AlignCenter); dialog_ex_set_text(dialog_ex, "Your edits will be lost!", 64, 32, AlignCenter, AlignCenter); dialog_ex_set_left_button_text(dialog_ex, "Cancel"); dialog_ex_set_right_button_text(dialog_ex, "Reset"); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index f94a65036..b57f7418a 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -379,6 +379,8 @@ static Desktop* desktop_alloc(void) { furi_record_create(RECORD_DESKTOP, desktop); + desktop->archive_dir = furi_string_alloc(); + return desktop; } @@ -494,6 +496,15 @@ void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled) { desktop->in_transition = false; } +void desktop_launch_archive(Desktop* desktop, const char* open_dir) { + if(open_dir) { + furi_string_set(desktop->archive_dir, open_dir); + } else { + furi_string_reset(desktop->archive_dir); + } + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive); +} + /* * Public API */ diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index 10badcc07..ee6b52e05 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -89,9 +89,12 @@ struct Desktop { FuriPubSub* ascii_events_pubsub; FuriPubSubSubscription* ascii_events_subscription; + + FuriString* archive_dir; }; void desktop_lock(Desktop* desktop, bool pin_lock); void desktop_unlock(Desktop* desktop); int32_t desktop_shutdown(void* context); void desktop_set_stealth_mode_state(Desktop* desktop, bool enabled); +void desktop_launch_archive(Desktop* desktop, const char* open_dir); diff --git a/applications/services/desktop/desktop_keybinds.c b/applications/services/desktop/desktop_keybinds.c index 62ed5c228..4a491a586 100644 --- a/applications/services/desktop/desktop_keybinds.c +++ b/applications/services/desktop/desktop_keybinds.c @@ -203,7 +203,7 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) { } else if(furi_string_equal(keybind, "Apps Menu")) { loader_start_detached_with_gui_error(desktop->loader, LOADER_APPLICATIONS_NAME, NULL); } else if(furi_string_equal(keybind, "Archive")) { - view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventOpenArchive); + desktop_launch_archive(desktop, NULL); } else if(furi_string_equal(keybind, "Clock")) { loader_start_detached_with_gui_error( desktop->loader, EXT_PATH("apps/Tools/nightstand.fap"), ""); @@ -218,11 +218,11 @@ void desktop_run_keybind(Desktop* desktop, InputType _type, InputKey _key) { } else if(furi_string_equal(keybind, "Wipe Device")) { loader_start_detached_with_gui_error(desktop->loader, "Storage", "Wipe Device"); } else { - if(storage_common_exists(desktop->storage, furi_string_get_cstr(keybind))) { - run_with_default_app(furi_string_get_cstr(keybind)); + const char* str = furi_string_get_cstr(keybind); + if(storage_common_exists(desktop->storage, str)) { + run_with_default_app(str); } else { - loader_start_detached_with_gui_error( - desktop->loader, furi_string_get_cstr(keybind), NULL); + loader_start_detached_with_gui_error(desktop->loader, str, NULL); } } diff --git a/applications/services/desktop/scenes/desktop_scene_main.c b/applications/services/desktop/scenes/desktop_scene_main.c index a1f4df3e8..3ebd69eeb 100644 --- a/applications/services/desktop/scenes/desktop_scene_main.c +++ b/applications/services/desktop/scenes/desktop_scene_main.c @@ -34,8 +34,10 @@ static void desktop_scene_main_interact_animation_callback(void* context) { } #ifdef APP_ARCHIVE -static void - desktop_switch_to_app(Desktop* desktop, const FlipperInternalApplication* flipper_app) { +static void desktop_switch_to_app( + Desktop* desktop, + const FlipperInternalApplication* flipper_app, + void* context) { furi_assert(desktop); furi_assert(flipper_app); furi_assert(flipper_app->app); @@ -56,6 +58,7 @@ static void furi_thread_set_name(desktop->scene_thread, flipper_app->name); furi_thread_set_stack_size(desktop->scene_thread, flipper_app->stack_size); furi_thread_set_callback(desktop->scene_thread, flipper_app->app); + furi_thread_set_context(desktop->scene_thread, context); furi_thread_start(desktop->scene_thread); } @@ -114,7 +117,7 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) { case DesktopMainEventOpenArchive: #ifdef APP_ARCHIVE - desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE); + desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE, desktop->archive_dir); #endif consumed = true; break; diff --git a/applications/services/dialogs/dialogs.c b/applications/services/dialogs/dialogs.c index 31856aa29..6f3a08885 100644 --- a/applications/services/dialogs/dialogs.c +++ b/applications/services/dialogs/dialogs.c @@ -16,6 +16,7 @@ void dialog_file_browser_set_basic_options( options->hide_ext = true; options->item_loader_callback = NULL; options->item_loader_context = NULL; + options->select_right = false; } static DialogsApp* dialogs_app_alloc(void) { diff --git a/applications/services/dialogs/dialogs.h b/applications/services/dialogs/dialogs.h index b8beddc5e..fc20d2eba 100644 --- a/applications/services/dialogs/dialogs.h +++ b/applications/services/dialogs/dialogs.h @@ -26,6 +26,7 @@ typedef struct DialogsApp DialogsApp; * @param hide_ext true - hide extensions for files * @param item_loader_callback callback function for providing custom icon & entry name * @param hide_ext callback context + * @param select_right true - select with right key, allows selecting directories */ typedef struct { const char* extension; @@ -36,6 +37,8 @@ typedef struct { bool hide_ext; FileBrowserLoadItemCallback item_loader_callback; void* item_loader_context; + + bool select_right; } DialogsFileBrowserOptions; /** diff --git a/applications/services/dialogs/dialogs_api.c b/applications/services/dialogs/dialogs_api.c index 0db99067d..0ed2d59af 100644 --- a/applications/services/dialogs/dialogs_api.c +++ b/applications/services/dialogs/dialogs_api.c @@ -43,6 +43,7 @@ bool dialog_file_browser_show( .item_callback = options ? options->item_loader_callback : NULL, .item_callback_context = options ? options->item_loader_context : NULL, .base_path = furi_string_get_cstr(base_path), + .select_right = options ? options->select_right : false, }}; DialogsAppReturn return_data; diff --git a/applications/services/dialogs/dialogs_message.h b/applications/services/dialogs/dialogs_message.h index 1c8c4fb50..17c2830be 100644 --- a/applications/services/dialogs/dialogs_message.h +++ b/applications/services/dialogs/dialogs_message.h @@ -18,6 +18,8 @@ typedef struct { FileBrowserLoadItemCallback item_callback; void* item_callback_context; const char* base_path; + + bool select_right; } DialogsAppMessageDataFileBrowser; typedef struct { diff --git a/applications/services/dialogs/dialogs_module_file_browser.c b/applications/services/dialogs/dialogs_module_file_browser.c index 603c27cff..77f3d0cef 100644 --- a/applications/services/dialogs/dialogs_module_file_browser.c +++ b/applications/services/dialogs/dialogs_module_file_browser.c @@ -46,6 +46,7 @@ bool dialogs_app_process_module_file_browser(const DialogsAppMessageDataFileBrow data->file_icon, data->hide_ext); file_browser_set_item_callback(file_browser, data->item_callback, data->item_callback_context); + file_browser_set_select_right(file_browser, data->select_right); file_browser_start(file_browser, data->preselected_filename); view_holder_set_view(view_holder, file_browser_get_view(file_browser)); diff --git a/applications/services/gui/modules/file_browser.c b/applications/services/gui/modules/file_browser.c index ef1d9b04e..0b0c1a35a 100644 --- a/applications/services/gui/modules/file_browser.c +++ b/applications/services/gui/modules/file_browser.c @@ -127,6 +127,8 @@ struct FileBrowser { FuriString* result_path; FuriTimer* scroll_timer; + + bool select_right; }; typedef struct { @@ -234,10 +236,11 @@ void file_browser_configure( furi_check(browser); browser->ext_filter = extension; - browser->skip_assets = skip_assets; - browser->hide_ext = hide_ext; browser->base_path = base_path; + browser->skip_assets = skip_assets; browser->hide_dot_files = hide_dot_files; + browser->hide_ext = hide_ext; + browser->select_right = false; with_view_model( browser->view, @@ -296,6 +299,11 @@ void file_browser_set_item_callback( browser->item_callback = callback; } +void file_browser_set_select_right(FileBrowser* browser, bool select_right) { + furi_check(browser); + browser->select_right = select_right; +} + static bool browser_is_item_in_array(FileBrowserModel* model, uint32_t idx) { size_t array_size = items_array_size(model->items); @@ -793,6 +801,31 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) { } consumed = true; } + } else if(event->key == InputKeyRight) { + if(event->type == InputTypeShort && browser->select_right) { + BrowserItem_t* selected_item = NULL; + with_view_model( + browser->view, + FileBrowserModel * model, + { + if(browser_is_item_in_array(model, model->item_idx)) { + selected_item = + items_array_get(model->items, model->item_idx - model->array_offset); + } + }, + false); + + if(selected_item) { + if(selected_item->type == BrowserItemTypeFile || + selected_item->type == BrowserItemTypeFolder) { + furi_string_set(browser->result_path, selected_item->path); + if(browser->callback) { + browser->callback(browser->context); + } + } + } + consumed = true; + } } else if(event->key == InputKeyLeft) { if(event->type == InputTypeShort) { bool is_root = false; diff --git a/applications/services/gui/modules/file_browser.h b/applications/services/gui/modules/file_browser.h index e8e9228f7..5c09e9031 100644 --- a/applications/services/gui/modules/file_browser.h +++ b/applications/services/gui/modules/file_browser.h @@ -48,6 +48,8 @@ void file_browser_set_item_callback( FileBrowserLoadItemCallback callback, void* context); +void file_browser_set_select_right(FileBrowser* browser, bool select_right); + #ifdef __cplusplus } #endif diff --git a/applications/services/loader/loader_menu.c b/applications/services/loader/loader_menu.c index d9a231ca0..adc95f05d 100644 --- a/applications/services/loader/loader_menu.c +++ b/applications/services/loader/loader_menu.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "loader.h" #include "loader_i.h" @@ -130,7 +131,12 @@ static void loader_menu_apps_callback(void* context, uint32_t index) { LoaderMenuApp* app = context; const MenuApp* menu_app = MenuAppList_get(app->apps_list, index); const char* name = menu_app->path ? menu_app->path : menu_app->name; - loader_menu_start(name); + + if(menu_app->path && !strstr(menu_app->path, ".fap")) { + run_with_default_app(menu_app->path); + } else { + loader_menu_start(name); + } } static void loader_menu_last_callback(void* context, uint32_t index) { @@ -225,6 +231,14 @@ static void loader_menu_add_app_entry( app); } +static const Icon* loader_menu_get_ext_icon(Storage* storage, const char* path) { + if(storage_dir_exists(storage, path)) return &I_dir_10px; + const char* ext = strrchr(path, '.'); + if(ext && strcasecmp(ext, ".js") == 0) return &I_js_script_10px; + + return &I_file_10px; +} + bool loader_menu_load_fap_meta( Storage* storage, FuriString* path, @@ -254,11 +268,9 @@ static void loader_menu_find_add_app(LoaderMenuApp* app, Storage* storage, FuriS if(furi_string_start_with(line, "/")) { path = strdup(furi_string_get_cstr(line)); if(!loader_menu_load_fap_meta(storage, line, line, &icon)) { - free((void*)path); - path = NULL; - } else { - name = strdup(furi_string_get_cstr(line)); + icon = loader_menu_get_ext_icon(storage, path); } + name = strdup(furi_string_get_cstr(line)); } else { for(size_t i = 0; !name && i < FLIPPER_APPS_COUNT; i++) { if(furi_string_equal(line, FLIPPER_APPS[i].name)) { diff --git a/applications/settings/desktop_settings/desktop_settings_app.h b/applications/settings/desktop_settings/desktop_settings_app.h index e763ba75a..5f28bad2b 100644 --- a/applications/settings/desktop_settings/desktop_settings_app.h +++ b/applications/settings/desktop_settings/desktop_settings_app.h @@ -31,7 +31,7 @@ typedef enum { typedef enum { DesktopSettingsAppKeybindActionTypeMainApp, DesktopSettingsAppKeybindActionTypeExternalApp, - DesktopSettingsAppKeybindActionTypeOpenFile, + DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory, DesktopSettingsAppKeybindActionTypeMoreActions, DesktopSettingsAppKeybindActionTypeRemoveKeybind, } DesktopSettingsAppKeybindActionType; diff --git a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action_type.c b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action_type.c index b3a001c2d..59e612f6a 100644 --- a/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action_type.c +++ b/applications/settings/desktop_settings/scenes/desktop_settings_scene_keybinds_action_type.c @@ -31,7 +31,7 @@ static void scene_manager_next_scene(app->scene_manager, DesktopSettingsAppSceneKeybindsAction); break; case DesktopSettingsAppKeybindActionTypeExternalApp: - case DesktopSettingsAppKeybindActionTypeOpenFile: { + case DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory: { const char* base_path; const char* extension; bool hide_ext; @@ -53,9 +53,10 @@ static void .item_loader_callback = keybinds_fap_selector_item_callback, .item_loader_context = app, .base_path = base_path, + .select_right = true, }; FuriString* temp_path = furi_string_alloc_set_str(base_path); - if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) { + if(storage_common_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) { furi_string_set(temp_path, keybind); } furi_record_close(RECORD_STORAGE); @@ -98,8 +99,8 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) { submenu_add_item( submenu, - "Open File", - DesktopSettingsAppKeybindActionTypeOpenFile, + "File / Directory (right btn)", + DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory, desktop_settings_scene_keybinds_action_type_submenu_callback, app); @@ -131,12 +132,15 @@ void desktop_settings_scene_keybinds_action_type_on_enter(void* context) { } } - if(storage_file_exists(furi_record_open(RECORD_STORAGE), furi_string_get_cstr(keybind))) { + Storage* storage = furi_record_open(RECORD_STORAGE); + if(storage_file_exists(storage, furi_string_get_cstr(keybind))) { if(furi_string_end_with_str(keybind, ".fap")) { selected = DesktopSettingsAppKeybindActionTypeExternalApp; } else { - selected = DesktopSettingsAppKeybindActionTypeOpenFile; + selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory; } + } else if(storage_dir_exists(storage, furi_string_get_cstr(keybind))) { + selected = DesktopSettingsAppKeybindActionTypeOpenFileOrDirectory; } furi_record_close(RECORD_STORAGE); diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 024ce9bec..da1683348 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1146,6 +1146,7 @@ Function,+,file_browser_free,void,FileBrowser* Function,+,file_browser_get_view,View*,FileBrowser* Function,+,file_browser_set_callback,void,"FileBrowser*, FileBrowserCallback, void*" Function,+,file_browser_set_item_callback,void,"FileBrowser*, FileBrowserLoadItemCallback, void*" +Function,+,file_browser_set_select_right,void,"FileBrowser*, _Bool" Function,+,file_browser_start,void,"FileBrowser*, FuriString*" Function,+,file_browser_stop,void,FileBrowser* Function,+,file_browser_worker_alloc,BrowserWorker*,"FuriString*, const char*, const char*, _Bool, _Bool"