Compare commits

...
Author SHA1 Message Date
David b6d8d67c6e Fix custom emulate decoded metadata display
Build Dev Firmware / build (push) Waiting to run
2026-07-18 15:22:59 +02:00
David ac8909904e Add custom emulate button metadata fallback
Build Dev Firmware / build (push) Canceled after 0s
2026-07-17 15:55:02 +02:00
3 changed files with 133 additions and 37 deletions
@@ -17,45 +17,46 @@ static const char* const button_default_labels[SUBGHZ_BUTTON_LABEL_COUNT] = {
typedef struct {
const char* protocol;
uint8_t max_custom_btn;
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"}},
{"VAG GROUP", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Porsche AG", 4, {"Original", "Lock", "Unlock", "Trunk", "Open"}},
{"FORD V0", 3, {"Original", "Lock", "Unlock", "Trunk"}},
{"Ford V2", 4, {"Unlock", "Lock", "Trunk", "Panic", "Remote Start"}},
{"PSA GROUP", 4, {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"PSA OLD", 4, {"Original", "Lock", "Unlock", "Trunk", "Trunk"}},
{"KIA/HYU V0", 4, {"Original", "Lock", "Unlock", "Trunk", "Horn"}},
{"KIA/HYU V1", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V2", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"KIA/HYU V3", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V4", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V3/V4", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Horn"}},
{"KIA/HYU V5", 4, {"Original", "Unlock", "Lock", "Trunk", "Horn"}},
{"KIA/HYU V6", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"SUBARU", 5, {"Original", "Lock", "Unlock", "Trunk", "Panic", "Extra"}},
{"SUZUKI", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Star Line", 4, {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Scher-Khan", 4, {"Original", "Lock", "Unlock", "Trunk", "Start"}},
{"Sheriff CFM", 4, {"Original", "Lock", "Unlock", "Trunk", "Panic"}},
{"Nice FloR-S", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"CAME Atomo", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Alutech AT-4N", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"KeeLoq", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Phoenix_V2", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Beninca ARC", 2, {"Original", "Btn 1", "Btn 2"}},
{"GangQi", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Hay21", 2, {"Original", "Btn 1", "Btn 2"}},
{"Hollarm", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Jarolift", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"KingGates Stylo4k", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Princeton", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Roger", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Security+ 2.0", 4, {"Original", "Btn 1", "Btn 2", "Btn 3", "Btn 4"}},
{"Somfy Telis", 3, {"Original", "Btn 1", "Btn 2", "Btn 3"}},
{"Faac SLH", 1, {"Original", "Btn 1"}},
};
void subghz_button_labels_reset(const char* labels[SUBGHZ_BUTTON_LABEL_COUNT]) {
@@ -98,3 +99,15 @@ const char* subghz_button_labels_get(
return "Button";
}
uint8_t subghz_button_labels_get_max_custom_btn(const char* protocol) {
if(!protocol) return 0;
for(uint8_t i = 0; i < COUNT_OF(protocol_button_labels); i++) {
if(strcmp(protocol, protocol_button_labels[i].protocol) == 0) {
return protocol_button_labels[i].max_custom_btn;
}
}
return 0;
}
@@ -19,6 +19,8 @@ const char* subghz_button_labels_get(
uint8_t custom_btn_id,
uint8_t original_custom_btn);
uint8_t subghz_button_labels_get_max_custom_btn(const char* protocol);
#ifdef __cplusplus
}
#endif
@@ -15,6 +15,7 @@
#include <notification/notification_messages.h>
#include "../helpers/subghz_txrx_i.h"
#include <lib/subghz/blocks/custom_btn_i.h>
#include <string.h>
#define TAG "SubGhzSceneCarEmulate"
#define MIN_TX_TICKS 66U /* ~666 ms at 100 ms tick */
@@ -30,6 +31,9 @@ typedef struct {
uint32_t freq;
char preset_short[12]; /* "AM650", "FM476", … */
bool has_serial;
bool has_counter;
/* TX state */
bool is_transmitting;
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
@@ -203,6 +207,73 @@ static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
furi_string_free(preset_str);
}
static bool car_emulate_hex_digit(char c, uint8_t* value) {
if(c >= '0' && c <= '9') {
*value = c - '0';
return true;
} else if(c >= 'a' && c <= 'f') {
*value = c - 'a' + 10;
return true;
} else if(c >= 'A' && c <= 'F') {
*value = c - 'A' + 10;
return true;
}
return false;
}
static bool car_emulate_parse_hex_after(
const char* text,
const char* marker,
uint32_t* value) {
const char* field = strstr(text, marker);
if(!field) return false;
field += strlen(marker);
while(*field == ' ' || *field == '\t' || *field == '[') {
field++;
}
if(field[0] == '0' && (field[1] == 'x' || field[1] == 'X')) {
field += 2;
}
uint32_t parsed = 0;
uint8_t digits = 0;
uint8_t digit_value = 0;
while((digits < 8) && car_emulate_hex_digit(*field, &digit_value)) {
parsed = (parsed << 4) | digit_value;
digits++;
field++;
}
if(digits == 0) return false;
*value = parsed;
return true;
}
static void car_emulate_apply_decoded_metadata(FuriString* decoded_text) {
furi_assert(s_state);
if(!decoded_text) return;
const char* text = furi_string_get_cstr(decoded_text);
uint32_t value = 0;
if(!s_state->has_serial &&
(car_emulate_parse_hex_after(text, "Sn:", &value) ||
car_emulate_parse_hex_after(text, "SN:", &value) ||
car_emulate_parse_hex_after(text, "Ser:", &value) ||
car_emulate_parse_hex_after(text, "Serial:", &value) ||
car_emulate_parse_hex_after(text, "Fix:", &value))) {
s_state->serial = value;
s_state->has_serial = true;
}
if(!s_state->has_counter && car_emulate_parse_hex_after(text, "Cnt:", &value)) {
s_state->original_counter = value;
s_state->current_counter = value;
s_state->has_counter = true;
}
}
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
static bool car_emulate_apply_button(SubGhz* subghz, InputKey key) {
UNUSED(subghz);
@@ -337,7 +408,8 @@ void subghz_scene_car_emulate_on_enter(void* context) {
}
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
s_state->has_serial =
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
flipper_format_rewind(fff);
uint32_t btn_tmp = 0;
@@ -346,7 +418,8 @@ void subghz_scene_car_emulate_on_enter(void* context) {
}
flipper_format_rewind(fff);
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
s_state->has_counter =
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
s_state->current_counter = s_state->original_counter;
furi_string_free(tmp);
@@ -370,6 +443,7 @@ void subghz_scene_car_emulate_on_enter(void* context) {
if(subghz_protocol_decoder_base_deserialize(decoder, fff) == SubGhzProtocolStatusOk) {
FuriString* decoded_text = furi_string_alloc();
subghz_protocol_decoder_base_get_string(decoder, decoded_text);
car_emulate_apply_decoded_metadata(decoded_text);
furi_string_free(decoded_text);
}
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
@@ -379,6 +453,13 @@ void subghz_scene_car_emulate_on_enter(void* context) {
s_state->max_custom_button =
subghz_custom_btn_is_allowed() ? subghz_custom_btn_get_max() : SUBGHZ_CUSTOM_BTN_OK;
if(s_state->max_custom_button == SUBGHZ_CUSTOM_BTN_OK) {
s_state->max_custom_button =
subghz_button_labels_get_max_custom_btn(s_state->protocol_name);
if(s_state->max_custom_button != SUBGHZ_CUSTOM_BTN_OK) {
subghz_custom_btn_set_max(s_state->max_custom_button);
}
}
const char* button_labels[SUBGHZ_BUTTON_LABEL_COUNT];
subghz_button_labels_reset(button_labels);