Compare commits

..
Author SHA1 Message Date
David d4d8b0e646 Initialize custom emulate button metadata 2026-07-17 15:49:57 +02:00
David 36483ec07d Align custom emulate button labels
Build Dev Firmware / build (push) Waiting to run
2026-07-17 14:12:38 +02:00
David 807e74b7d6 Use custom emulate UI when enabled
Build Dev Firmware / build (push) Waiting to run
2026-07-17 13:47:28 +02:00
6 changed files with 193 additions and 93 deletions
@@ -0,0 +1,100 @@
#include "subghz_button_labels.h"
#include <furi.h>
#include <lib/subghz/blocks/custom_btn.h>
#include <string.h>
static const char* const button_default_labels[SUBGHZ_BUTTON_LABEL_COUNT] = {
"Original",
"Up",
"Down",
"Left",
"Right",
"Button 5",
"Button 6",
"Button 7",
};
typedef struct {
const char* protocol;
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT];
} SubGhzProtocolButtonLabels;
static const SubGhzProtocolButtonLabels protocol_button_labels[] = {
{"VAG GROUP", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Porsche AG", {"Original", "Lock", "Unlock", "Trunk", "Open"}},
{"FORD V0", {"Original", "Lock", "Unlock", "Trunk"}},
{"Ford V2", {"Unlock", "Lock", "Trunk", "Panic", "Remote Start"}},
{"PSA GROUP", {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"PSA OLD", {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"KIA/HYU V0", {"Original", "Lock", "Unlock", "Trunk", "Horn"}},
{"KIA/HYU V1", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V2", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V3", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V4", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V3/V4", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V5", {"Original", "Unlock", "Lock", "Trunk", "Horn"}},
{"KIA/HYU V6", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"SUBARU", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Extra"}},
{"SUZUKI", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Star Line", {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Scher-Khan", {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Sheriff CFM", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Nice FloR-S", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"CAME Atomo", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Alutech AT-4N", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"KeeLoq", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Phoenix_V2", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Beninca ARC", {"Original", "Btn 1", "Btn 2"}},
{"GangQi", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Hay21", {"Original", "Btn 1", "Btn 2"}},
{"Hollarm", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Jarolift", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"KingGates Stylo4k", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Princeton", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Roger", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Security+ 2.0", {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Somfy Telis", {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Faac SLH", {"Original", "Btn 1"}},
};
void subghz_button_labels_reset(const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]) {
for(uint8_t i = 0; i < SUBGHZ_BUTTON_LABEL_COUNT; i++) {
labels[i] = button_default_labels[i];
}
}
void subghz_button_labels_apply_protocol(
const char* protocol,
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]) {
if(!protocol) return;
for(uint8_t i = 0; i < COUNT_OF(protocol_button_labels); i++) {
if(strcmp(protocol, protocol_button_labels[i].protocol) == 0) {
for(uint8_t btn = 0; btn < SUBGHZ_BUTTON_LABEL_COUNT; btn++) {
if(protocol_button_labels[i].labels[btn]) {
labels[btn] = protocol_button_labels[i].labels[btn];
}
}
break;
}
}
}
const char* subghz_button_labels_get(
const char* const labels[SUBGHZ_BUTTON_LABEL_COUNT],
uint8_t custom_btn_id,
uint8_t original_custom_btn) {
if(custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) {
if((original_custom_btn != SUBGHZ_CUSTOM_BTN_OK) &&
(original_custom_btn < SUBGHZ_BUTTON_LABEL_COUNT)) {
return labels[original_custom_btn];
}
}
if(custom_btn_id < SUBGHZ_BUTTON_LABEL_COUNT) {
return labels[custom_btn_id];
}
return "Button";
}
@@ -0,0 +1,24 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define SUBGHZ_BUTTON_LABEL_COUNT 8
void subghz_button_labels_reset(const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]);
void subghz_button_labels_apply_protocol(
const char* protocol,
const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]);
const char* subghz_button_labels_get(
const char* const labels[SUBGHZ_BUTTON_LABEL_COUNT],
uint8_t custom_btn_id,
uint8_t original_custom_btn);
#ifdef __cplusplus
}
#endif
@@ -9,6 +9,7 @@
*/
#include "../subghz_i.h"
#include "../views/subghz_car_emulate.h"
#include "../helpers/subghz_button_labels.h"
#include "../helpers/subghz_custom_event.h"
#include <lib/subghz/blocks/generic.h>
#include <notification/notification_messages.h>
@@ -36,6 +37,7 @@ typedef struct {
/* Resolved custom button repeated while the physical key is held */
uint8_t pending_button;
uint8_t max_custom_button;
} CarEmulateState;
static CarEmulateState* s_state = NULL;
@@ -202,7 +204,7 @@ static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
}
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
static void car_emulate_apply_button(SubGhz* subghz, InputKey key) {
static bool car_emulate_apply_button(SubGhz* subghz, InputKey key) {
UNUSED(subghz);
uint8_t custom_btn_id;
@@ -215,7 +217,11 @@ static void car_emulate_apply_button(SubGhz* subghz, InputKey key) {
default: custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; break;
}
subghz_custom_btn_set(custom_btn_id);
if(custom_btn_id > s_state->max_custom_button) {
return false;
}
return subghz_custom_btn_set(custom_btn_id);
}
@@ -361,19 +367,43 @@ void subghz_scene_car_emulate_on_enter(void* context) {
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
if(decoder && fff) {
flipper_format_rewind(fff);
subghz_protocol_decoder_base_deserialize(decoder, fff);
if(subghz_protocol_decoder_base_deserialize(decoder, fff) == SubGhzProtocolStatusOk) {
FuriString* decoded_text = furi_string_alloc();
subghz_protocol_decoder_base_get_string(decoder, decoded_text);
furi_string_free(decoded_text);
}
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
* start from the beginning of the file. */
flipper_format_rewind(fff);
}
subghz_car_emulate_view_set_labels(
s_state->max_custom_button =
subghz_custom_btn_is_allowed() ? subghz_custom_btn_get_max() : SUBGHZ_CUSTOM_BTN_OK;
const char* button_labels[SUBGHZ_BUTTON_LABEL_COUNT];
subghz_button_labels_reset(button_labels);
subghz_button_labels_apply_protocol(s_state->protocol_name, button_labels);
subghz_car_emulate_view_set_labels(
subghz->car_emulate_view,
"UNLOCK", /* OK */
"LOCK", /* Up */
"TRUNK", /* Down */
"PANIC", /* Left */
"START" /* Right */
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_OK, subghz_custom_btn_get_original()),
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_UP ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_UP, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_DOWN ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_DOWN, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_LEFT ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_LEFT, subghz_custom_btn_get_original()) :
"",
s_state->max_custom_button >= SUBGHZ_CUSTOM_BTN_RIGHT ?
subghz_button_labels_get(
button_labels, SUBGHZ_CUSTOM_BTN_RIGHT, subghz_custom_btn_get_original()) :
""
);
car_emulate_read_freq_preset(subghz, s_state);
@@ -407,14 +437,18 @@ bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
car_emulate_stop_tx(subghz);
}
/* Bump counter */
s_state->current_counter++;
/* Set the custom button BEFORE deserialize() is called inside
* subghz_tx_start() → subghz_txrx_tx_start().
* The protocol's deserialize() will call subghz_custom_btn_get()
* to pick the right button code. */
car_emulate_apply_button(subghz, key);
if(!car_emulate_apply_button(subghz, key)) {
notification_message(subghz->notifications, &sequence_error);
car_emulate_refresh_view(subghz);
return true;
}
/* Bump counter */
s_state->current_counter++;
/* Only update the counter in fff_data; the protocol handles Btn. */
car_emulate_update_fff(subghz, s_state->current_counter);
@@ -121,17 +121,7 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
scene_manager_set_scene_state(
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
bool use_custom = subghz->last_settings->custom_car_emulate;
if(use_custom) {
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
uint32_t cnt_tmp = 0;
flipper_format_rewind(fff);
if(!flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
use_custom = false;
}
}
if(use_custom) {
if(subghz->last_settings->custom_car_emulate) {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
} else {
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
@@ -1,5 +1,6 @@
#include "../subghz_i.h"
#include "subghz/types.h"
#include "../helpers/subghz_button_labels.h"
#include "../helpers/subghz_custom_event.h"
#include <lib/toolbox/value_index.h>
#include <machine/endian.h>
@@ -81,18 +82,7 @@ static const uint8_t button_value[] = {
7,
};
#define BUTTON_VALUE_COUNT (sizeof(button_value) / sizeof(button_value[0]))
static const char* const button_default_labels[BUTTON_VALUE_COUNT] = {
"Original",
"Up",
"Down",
"Left",
"Right",
"Button 5",
"Button 6",
"Button 7",
};
#define BUTTON_VALUE_COUNT SUBGHZ_BUTTON_LABEL_COUNT
static const char* button_labels[BUTTON_VALUE_COUNT] = {
"Original",
@@ -105,30 +95,6 @@ static const char* button_labels[BUTTON_VALUE_COUNT] = {
"Button 7",
};
typedef struct {
const char* protocol;
const char* labels[BUTTON_VALUE_COUNT];
} ProtocolButtonLabels;
static const ProtocolButtonLabels protocol_button_labels[] = {
{"VAG GROUP", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Porsche AG", {"Original", "Lock", "Unlock", "Trunk", "Open"}},
{"FORD V0", {"Original", "Lock", "Unlock", "Trunk"}},
{"Ford V2", {"Unlock", "Lock", "Trunk", "Panic", "Remote Start"}},
{"PSA GROUP", {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"KIA/HYU V0", {"Original", "Lock", "Unlock", "Trunk", "Horn"}},
{"KIA/HYU V1", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V2", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V3/V4", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V5", {"Original", "Unlock", "Lock", "Trunk", "Horn"}},
{"KIA/HYU V6", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"SUBARU", {"Original", "Lock", "Unlock", "Trunk", "Panic", "Extra"}},
{"SUZUKI", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Star Line", {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Scher-Khan", {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Sheriff CFM", {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
};
typedef struct {
char* name;
uint8_t mode_count;
@@ -146,35 +112,16 @@ static Protocols protocols[] = {
#define PROTOCOLS_COUNT (sizeof(protocols) / sizeof(Protocols))
static void subghz_scene_signal_settings_reset_button_labels(void) {
for(uint8_t i = 0; i < BUTTON_VALUE_COUNT; i++) {
button_labels[i] = button_default_labels[i];
}
subghz_button_labels_reset(button_labels);
}
static void subghz_scene_signal_settings_apply_button_labels(const char* protocol) {
for(uint8_t i = 0; i < COUNT_OF(protocol_button_labels); i++) {
if(strcmp(protocol, protocol_button_labels[i].protocol) == 0) {
for(uint8_t btn = 0; btn < BUTTON_VALUE_COUNT; btn++) {
if(protocol_button_labels[i].labels[btn]) {
button_labels[btn] = protocol_button_labels[i].labels[btn];
}
}
break;
}
}
subghz_button_labels_apply_protocol(protocol, button_labels);
}
static const char* subghz_scene_signal_settings_get_button_label(uint8_t custom_btn_id) {
if(custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) {
uint8_t original = subghz_custom_btn_get_original();
if((original != SUBGHZ_CUSTOM_BTN_OK) && (original < BUTTON_VALUE_COUNT)) {
return button_labels[original];
}
}
if(custom_btn_id < BUTTON_VALUE_COUNT) {
return button_labels[custom_btn_id];
}
return "Button";
return subghz_button_labels_get(
button_labels, custom_btn_id, subghz_custom_btn_get_original());
}
static bool subghz_scene_signal_settings_update_uint32_field(
@@ -17,11 +17,11 @@ typedef struct {
char preset[12];
bool is_transmitting;
uint8_t anim_frame;
char label_ok[12];
char label_up[12];
char label_down[12];
char label_left[12];
char label_right[12];
char label_ok[16];
char label_up[16];
char label_down[16];
char label_left[16];
char label_right[16];
} SubGhzCarEmulateViewModel;
/* ── Handle ─────────────────────────────────────────────────────────────── */
@@ -79,7 +79,7 @@ static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
const uint8_t font_h = canvas_current_font_height(canvas);
/* Centre → UNLOCK (OK button) */
{
if(m->label_ok[0]) {
const char* lbl = m->label_ok;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 45 - font_h / 2, w, font_h, 3);
@@ -89,7 +89,7 @@ static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
}
/* Up → LOCK */
{
if(m->label_up[0]) {
const char* lbl = m->label_up;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 33 - font_h / 2, w, font_h, 3);
@@ -99,7 +99,7 @@ static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
}
/* Left → PANIC */
{
if(m->label_left[0]) {
const char* lbl = m->label_left;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 0, 46 - font_h / 2, w, font_h, 3);
@@ -109,7 +109,7 @@ static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
}
/* Right → generic extra */
{
if(m->label_right[0]) {
const char* lbl = m->label_right;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 127 - w, 46 - font_h / 2, w, font_h, 3);
@@ -119,7 +119,7 @@ static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
}
/* Down → BOOT */
{
if(m->label_down[0]) {
const char* lbl = m->label_down;
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
canvas_draw_rbox(canvas, 64 - w / 2, 57 - font_h / 2, w, font_h, 3);
@@ -259,6 +259,11 @@ void subghz_car_emulate_view_set_labels(
strncpy(m->label_down, down ? down : "", sizeof(m->label_down) - 1);
strncpy(m->label_left, left ? left : "", sizeof(m->label_left) - 1);
strncpy(m->label_right, right ? right : "", sizeof(m->label_right) - 1);
m->label_ok[sizeof(m->label_ok) - 1] = '\0';
m->label_up[sizeof(m->label_up) - 1] = '\0';
m->label_down[sizeof(m->label_down) - 1] = '\0';
m->label_left[sizeof(m->label_left) - 1] = '\0';
m->label_right[sizeof(m->label_right) - 1] = '\0';
},
true);
}