Compare commits

..

3 Commits

Author SHA1 Message Date
David 069a42d697 Repeat car emulate TX while held
Build Dev Firmware / build (push) Waiting to run
2026-07-12 11:24:21 +02:00
David 2a182a2f5c Allow saved KIA V5 captures to reopen
Build Dev Firmware / build (push) Has been cancelled
2026-07-11 18:33:15 +02:00
David b13f42f8bc Fix KeeLoq and KIA V5 replay handling
Build Dev Firmware / build (push) Has been cancelled
2026-07-11 18:00:58 +02:00
3 changed files with 62 additions and 12 deletions
@@ -34,7 +34,7 @@ typedef struct {
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
uint32_t tx_start_tick;
/* Pending button key (InputKey) decoded from the packed custom event */
/* Resolved custom button repeated while the physical key is held */
uint8_t pending_button;
} CarEmulateState;
@@ -252,12 +252,34 @@ static bool car_emulate_start_tx(SubGhz* subghz, uint8_t custom_btn_id) {
/** Stop an active transmission. */
static void car_emulate_stop_tx(SubGhz* subghz) {
subghz_block_generic_global.endless_tx = false;
subghz_txrx_stop(subghz->txrx);
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
FURI_LOG_I(TAG, "TX stopped");
}
static bool car_emulate_restart_tx(SubGhz* subghz) {
furi_assert(s_state);
car_emulate_update_fff(subghz, s_state->current_counter);
subghz_block_generic_global.endless_tx = true;
if(car_emulate_start_tx(subghz, s_state->pending_button)) {
s_state->is_transmitting = true;
subghz->state_notifications = SubGhzNotificationStateTx;
notification_message(subghz->notifications, &sequence_blink_magenta_10);
FURI_LOG_D(TAG, "TX restarted while button is held");
return true;
}
subghz_block_generic_global.endless_tx = false;
s_state->is_transmitting = false;
s_state->stop_pending = false;
notification_message(subghz->notifications, &sequence_error);
return false;
}
/* ═══════════════════════════════════════════════════════════════════════════
* View callback (fired from the View's input handler)
* ═════════════════════════════════════════════════════════════════════════*/
@@ -293,6 +315,7 @@ void subghz_scene_car_emulate_on_enter(void* context) {
s_state = malloc(sizeof(CarEmulateState));
furi_check(s_state);
memset(s_state, 0, sizeof(CarEmulateState));
subghz_block_generic_global.endless_tx = false;
/* ── Read metadata from the loaded fff_data ── */
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
@@ -401,8 +424,11 @@ bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
s_state->tx_start_tick = (uint32_t)furi_get_tick();
uint8_t cur_btn = subghz_custom_btn_get();
s_state->pending_button = cur_btn;
subghz_block_generic_global.endless_tx = true;
if(!car_emulate_start_tx(subghz, cur_btn)) {
s_state->is_transmitting = false;
subghz_block_generic_global.endless_tx = false;
notification_message(subghz->notifications, &sequence_error);
}
@@ -411,11 +437,13 @@ bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
/* ── Stop ── */
} else if(event.event == SubGhzCustomEventCarEmulateStop) {
if(s_state->is_transmitting &&
subghz->state_notifications == SubGhzNotificationStateTx) {
if(s_state->is_transmitting) {
subghz_block_generic_global.endless_tx = false;
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
if(elapsed >= MIN_TX_TICKS) {
if(
elapsed >= MIN_TX_TICKS &&
subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
s_state->is_transmitting = false;
s_state->stop_pending = false;
@@ -431,6 +459,7 @@ bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
if(subghz->state_notifications == SubGhzNotificationStateTx) {
car_emulate_stop_tx(subghz);
}
subghz_block_generic_global.endless_tx = false;
scene_manager_search_and_switch_to_previous_scene(
subghz->scene_manager, SubGhzSceneSavedMenu);
consumed = true;
@@ -450,6 +479,8 @@ bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
s_state->is_transmitting = false;
s_state->stop_pending = false;
notification_message(subghz->notifications, &sequence_blink_stop);
} else {
car_emulate_restart_tx(subghz);
}
} else {
/* Still transmitting blink LED */
@@ -485,6 +516,7 @@ void subghz_scene_car_emulate_on_exit(void* context) {
car_emulate_stop_tx(subghz);
}
subghz_block_generic_global.endless_tx = false;
subghz->state_notifications = SubGhzNotificationStateIDLE;
notification_message(subghz->notifications, &sequence_blink_stop);
@@ -187,12 +187,15 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
if(!subghz->keeloq_keys_manager) {
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
}
uint16_t learning_type = ctx->recovered_type ?
ctx->recovered_type :
KEELOQ_LEARNING_SIMPLE;
char key_name[24];
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
subghz_keeloq_keys_add(
subghz->keeloq_keys_manager,
ctx->recovered_mfkey,
KEELOQ_LEARNING_SIMPLE,
learning_type,
key_name);
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
@@ -202,7 +205,7 @@ bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
entry->name = furi_string_alloc_set(key_name);
entry->key = ctx->recovered_mfkey;
entry->type = KEELOQ_LEARNING_SIMPLE;
entry->type = learning_type;
return true;
} else if(event.event == KL_DECRYPT_EVENT_DONE) {
+21 -6
View File
@@ -95,8 +95,7 @@ static uint16_t mixer_decode(uint32_t encrypted) {
return (s0 + (s1 << 8)) & 0xFFFF;
}
static __attribute__((unused)) uint32_t mixer_encode(
uint32_t serial, uint16_t counter, uint8_t button) {
static uint32_t mixer_encode(uint32_t serial, uint16_t counter, uint8_t button) {
uint8_t s0 = (uint8_t)(((serial >> 8) & 0x0FU) | ((button & 0x0FU) << 4));
uint8_t s1 = (uint8_t)((counter >> 8) & 0xFFU);
uint8_t s2 = (uint8_t)(serial & 0xFFU);
@@ -156,6 +155,17 @@ static __attribute__((unused)) uint32_t mixer_encode(
return ((uint32_t)s0 << 24) | ((uint32_t)s2 << 16) | ((uint32_t)s1 << 8) | (uint32_t)s3;
}
static uint64_t kia_v5_encode_data(uint32_t serial, uint16_t counter, uint8_t button) {
serial &= 0x0FFFFFFFU;
button &= 0x0FU;
const uint32_t encrypted = mixer_encode(serial, counter, button);
const uint64_t yek =
((uint64_t)button << 60) | ((uint64_t)serial << 32) | (uint64_t)encrypted;
return bit_reverse_64(yek);
}
struct SubGhzProtocolDecoderKiaV5 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
@@ -326,6 +336,8 @@ SubGhzProtocolStatus
}
subghz_custom_btn_set_max(4);
instance->generic.data = kia_v5_encode_data(
instance->generic.serial, instance->generic.cnt, instance->generic.btn);
instance->replay_data = instance->generic.data;
instance->replay_crc = kia_v5_calculate_crc(instance->replay_data);
@@ -675,10 +687,13 @@ SubGhzProtocolStatus
furi_assert(context);
SubGhzProtocolDecoderKiaV5* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_kia_v5_const.min_count_bit_for_found);
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if((ret == SubGhzProtocolStatusOk) &&
(instance->generic.data_count_bit <
subghz_protocol_kia_v5_const.min_count_bit_for_found)) {
ret = SubGhzProtocolStatusErrorParserBitCount;
}
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);