Compare commits
1 Commits
dev-360566
...
dev-8bf12d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bf12df45d |
@@ -93,7 +93,6 @@ typedef enum {
|
||||
SubGhzViewIdFrequencyAnalyzer,
|
||||
SubGhzViewIdReadRAW,
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
|
||||
} SubGhzViewId;
|
||||
|
||||
|
||||
@@ -30,7 +30,4 @@ ADD_SCENE(subghz, protocol_list, ProtocolList)
|
||||
ADD_SCENE(subghz, keeloq_keys, KeeloqKeys)
|
||||
ADD_SCENE(subghz, keeloq_key_edit, KeeloqKeyEdit)
|
||||
ADD_SCENE(subghz, psa_decrypt, PsaDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_decrypt, KeeloqDecrypt)
|
||||
ADD_SCENE(subghz, keeloq_bf2, KeeloqBf2)
|
||||
ADD_SCENE(subghz, kl_bf_cleanup, KlBfCleanup)
|
||||
ADD_SCENE(subghz, counter_bf, CounterBf)
|
||||
|
||||
@@ -5,10 +5,9 @@
|
||||
#define TAG "SubGhzCounterBf"
|
||||
|
||||
// How many ticks to wait between transmissions (1 tick ~100ms)
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 5
|
||||
#define COUNTER_BF_TX_INTERVAL_TICKS 3
|
||||
|
||||
typedef enum {
|
||||
CounterBfStateWarning,
|
||||
CounterBfStateIdle,
|
||||
CounterBfStateRunning,
|
||||
CounterBfStateStopped,
|
||||
@@ -23,16 +22,8 @@ typedef struct {
|
||||
uint32_t tick_wait;
|
||||
} CounterBfContext;
|
||||
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
#define CounterBfEventWarningOk (0xC2)
|
||||
|
||||
static void counter_bf_warning_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
if(result == GuiButtonTypeCenter && type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, CounterBfEventWarningOk);
|
||||
}
|
||||
}
|
||||
#define CounterBfEventStart (0xC0)
|
||||
#define CounterBfEventStop (0xC1)
|
||||
|
||||
static void counter_bf_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
@@ -41,36 +32,18 @@ static void counter_bf_widget_callback(GuiButtonType result, InputType type, voi
|
||||
}
|
||||
}
|
||||
|
||||
static void counter_bf_draw_warning(SubGhz* subghz) {
|
||||
widget_reset(subghz->widget);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget,
|
||||
64,
|
||||
20,
|
||||
AlignCenter,
|
||||
AlignCenter,
|
||||
FontSecondary,
|
||||
"WARNING:\nThis may desync\nyour fob!");
|
||||
widget_add_button_element(
|
||||
subghz->widget,
|
||||
GuiButtonTypeCenter,
|
||||
"OK",
|
||||
counter_bf_warning_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
widget_reset(subghz->widget);
|
||||
FuriString* str = furi_string_alloc();
|
||||
furi_string_printf(
|
||||
str,
|
||||
"Counter BruteForce\n"
|
||||
"Cnt: 0x%06lX\n"
|
||||
"Start: 0x%06lX\n"
|
||||
"Sent: %lu",
|
||||
ctx->current_cnt & 0xFFFFFF,
|
||||
ctx->start_cnt & 0xFFFFFF,
|
||||
ctx->packets_sent);
|
||||
"Cnt: 0x%08lX\n"
|
||||
"Sent: %lu pkts\n"
|
||||
"Start: 0x%08lX",
|
||||
ctx->current_cnt,
|
||||
ctx->packets_sent,
|
||||
ctx->start_cnt);
|
||||
widget_add_string_multiline_element(
|
||||
subghz->widget, 0, 0, AlignLeft, AlignTop, FontSecondary, furi_string_get_cstr(str));
|
||||
furi_string_free(str);
|
||||
@@ -84,12 +57,14 @@ static void counter_bf_draw(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
}
|
||||
|
||||
static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
// Escribir el Cnt final directamente en el archivo .sub en disco.
|
||||
// No usar subghz_save_protocol_to_file() porque ese serializa el estado
|
||||
// actual del encoder (que puede tener el Cnt ya incrementado internamente).
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
uint32_t cnt = ctx->current_cnt & 0xFFFFFF;
|
||||
if(!flipper_format_update_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
if(!flipper_format_update_uint32(file_fff, "Cnt", &ctx->current_cnt, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to update Cnt in .sub file");
|
||||
}
|
||||
} else {
|
||||
@@ -102,15 +77,16 @@ static void counter_bf_save(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
static void counter_bf_send(SubGhz* subghz, CounterBfContext* ctx) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
uint32_t delta = (ctx->current_cnt - ctx->start_cnt) & 0xFFFFFF;
|
||||
furi_hal_subghz_set_rolling_counter_mult((int32_t)delta);
|
||||
subghz_block_generic_global_counter_override_set(ctx->current_cnt & 0xFFFFFF);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
|
||||
uint32_t repeat = 20;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Repeat", &repeat, 1);
|
||||
|
||||
// Actualizar Cnt DESPUES de Repeat (update es secuencial en el buffer)
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_update_uint32(fff, "Cnt", &ctx->current_cnt, 1);
|
||||
|
||||
subghz_tx_start(subghz, fff);
|
||||
|
||||
ctx->packets_sent++;
|
||||
@@ -122,38 +98,42 @@ void subghz_scene_counter_bf_on_enter(void* context) {
|
||||
|
||||
CounterBfContext* ctx = malloc(sizeof(CounterBfContext));
|
||||
memset(ctx, 0, sizeof(CounterBfContext));
|
||||
ctx->state = CounterBfStateWarning;
|
||||
ctx->state = CounterBfStateIdle;
|
||||
ctx->step = 1;
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
// FIX: Leer el Cnt DIRECTAMENTE del archivo en disco con un FlipperFormat
|
||||
// propio, completamente separado del fff en memoria (que puede tener el Cnt
|
||||
// modificado por TXs previas y no refleja el estado real del .sub).
|
||||
{
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Cnt not in fff after key_load, reading from disk");
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt & 0xFFFFFF;
|
||||
ctx->start_cnt = cnt & 0xFFFFFF;
|
||||
}
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* file_fff = flipper_format_buffered_file_alloc(storage);
|
||||
if(flipper_format_buffered_file_open_existing(
|
||||
file_fff, furi_string_get_cstr(subghz->file_path))) {
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(file_fff, "Cnt", &cnt, 1)) {
|
||||
ctx->current_cnt = cnt;
|
||||
ctx->start_cnt = cnt;
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Cnt field not found in file");
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Failed to open .sub file for Cnt read");
|
||||
}
|
||||
flipper_format_free(file_fff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneCounterBf, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
counter_bf_draw_warning(subghz);
|
||||
// Deshabilitar auto-increment del protocolo para controlar el Cnt manualmente
|
||||
furi_hal_subghz_set_rolling_counter_mult(0);
|
||||
|
||||
// Recargar el protocolo DESPUES de haber leído el Cnt del disco,
|
||||
// para preparar el fff para TX sin que pise nuestro valor leído.
|
||||
subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), false);
|
||||
|
||||
counter_bf_draw(subghz, ctx);
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
@@ -164,21 +144,15 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == CounterBfEventWarningOk) {
|
||||
ctx->state = CounterBfStateIdle;
|
||||
counter_bf_draw(subghz, ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(event.event == CounterBfEventStart) {
|
||||
if(ctx->state == CounterBfStateWarning) return true;
|
||||
|
||||
if(ctx->state != CounterBfStateRunning) {
|
||||
ctx->state = CounterBfStateRunning;
|
||||
ctx->tick_wait = 0;
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
counter_bf_send(subghz, ctx);
|
||||
} else {
|
||||
// FIX 2: Al detener, guardar el contador actual en el .sub
|
||||
// para que al volver a emular manualmente continúe desde acá.
|
||||
ctx->state = CounterBfStateStopped;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
@@ -193,24 +167,19 @@ bool subghz_scene_counter_bf_on_event(void* context, SceneManagerEvent event) {
|
||||
if(ctx->tick_wait > 0) {
|
||||
ctx->tick_wait--;
|
||||
} else {
|
||||
ctx->current_cnt = (ctx->current_cnt + ctx->step) & 0xFFFFFF;
|
||||
ctx->current_cnt += ctx->step;
|
||||
counter_bf_send(subghz, ctx);
|
||||
counter_bf_save(subghz, ctx);
|
||||
counter_bf_draw(subghz, ctx);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
if(ctx->state == CounterBfStateWarning) {
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
|
||||
// FIX 2 (también en Back): guardar siempre al salir
|
||||
counter_bf_save(subghz, ctx);
|
||||
|
||||
furi_hal_subghz_set_rolling_counter_mult(1);
|
||||
free(ctx);
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
enum {
|
||||
KlBf2IndexLoadSig1,
|
||||
KlBf2IndexLoadSig2,
|
||||
KlBf2IndexType,
|
||||
KlBf2IndexStartBf,
|
||||
};
|
||||
|
||||
static const char* kl_bf2_type_labels[] = {
|
||||
"Type: Auto (6>7>8)",
|
||||
"Type: 6 (Serial 1)",
|
||||
"Type: 7 (Serial 2)",
|
||||
"Type: 8 (Serial 3)",
|
||||
};
|
||||
static const uint8_t kl_bf2_type_values[] = {0, 6, 7, 8};
|
||||
|
||||
static bool kl_bf2_extract_key(SubGhz* subghz, uint32_t* out_fix, uint32_t* out_hop) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
uint8_t key_data[8] = {0};
|
||||
if(!flipper_format_read_hex(fff, "Key", key_data, 8)) return false;
|
||||
*out_fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
*out_hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool kl_bf2_is_keeloq(SubGhz* subghz) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
FuriString* proto = furi_string_alloc();
|
||||
bool ok = flipper_format_read_string(fff, "Protocol", proto) &&
|
||||
furi_string_equal_str(proto, "KeeLoq");
|
||||
furi_string_free(proto);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void kl_bf2_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static bool kl_bf2_load_signal(SubGhz* subghz, FuriString* out_path) {
|
||||
DialogsFileBrowserOptions browser_options;
|
||||
dialog_file_browser_set_basic_options(
|
||||
&browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
|
||||
browser_options.base_path = SUBGHZ_APP_FOLDER;
|
||||
|
||||
FuriString* selected = furi_string_alloc();
|
||||
furi_string_set(selected, SUBGHZ_APP_FOLDER);
|
||||
|
||||
bool res = dialog_file_browser_show(subghz->dialogs, selected, selected, &browser_options);
|
||||
|
||||
if(res) {
|
||||
res = subghz_key_load(subghz, furi_string_get_cstr(selected), true);
|
||||
if(res) {
|
||||
furi_string_set(out_path, selected);
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(selected);
|
||||
return res;
|
||||
}
|
||||
|
||||
static void kl_bf2_rebuild_menu(SubGhz* subghz) {
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
char label1[64];
|
||||
char label2[64];
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig1_path, name, true);
|
||||
snprintf(label1, sizeof(label1), "Sig 1: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label1, sizeof(label1), "Load Signal 1");
|
||||
}
|
||||
|
||||
if(subghz->keeloq_bf2.sig2_loaded) {
|
||||
FuriString* name = furi_string_alloc();
|
||||
path_extract_filename(subghz->keeloq_bf2.sig2_path, name, true);
|
||||
snprintf(label2, sizeof(label2), "Sig 2: %s", furi_string_get_cstr(name));
|
||||
furi_string_free(name);
|
||||
} else {
|
||||
snprintf(label2, sizeof(label2), "Load Signal 2");
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu, label1, KlBf2IndexLoadSig1,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu, label2, KlBf2IndexLoadSig2,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
int type_idx = 0;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(kl_bf2_type_values[i] == subghz->keeloq_bf2.learn_type) {
|
||||
type_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
submenu_add_item(
|
||||
subghz->submenu, kl_bf2_type_labels[type_idx], KlBf2IndexType,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
|
||||
if(subghz->keeloq_bf2.sig1_loaded && subghz->keeloq_bf2.sig2_loaded) {
|
||||
submenu_add_item(
|
||||
subghz->submenu, "Start BF", KlBf2IndexStartBf,
|
||||
kl_bf2_submenu_callback, subghz);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.learn_type = 0;
|
||||
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_bf2_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KlBf2IndexLoadSig1) {
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix, hop;
|
||||
if(!kl_bf2_extract_key(subghz, &fix, &hop)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.fix = fix;
|
||||
subghz->keeloq_bf2.hop1 = hop;
|
||||
subghz->keeloq_bf2.serial = fix & 0x0FFFFFFF;
|
||||
subghz->keeloq_bf2.sig1_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig1_path, path);
|
||||
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexLoadSig2) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Load Signal 1 first");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriString* path = furi_string_alloc();
|
||||
if(kl_bf2_load_signal(subghz, path)) {
|
||||
if(!kl_bf2_is_keeloq(subghz)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Not a KeeLoq\nprotocol file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t fix2, hop2;
|
||||
if(!kl_bf2_extract_key(subghz, &fix2, &hop2)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot read Key\nfrom file");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t serial2 = fix2 & 0x0FFFFFFF;
|
||||
if(serial2 != subghz->keeloq_bf2.serial) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Serial mismatch!\nMust be same remote");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(hop2 == subghz->keeloq_bf2.hop1) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Same hop code!\nUse a different\ncapture");
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
subghz->keeloq_bf2.hop2 = hop2;
|
||||
subghz->keeloq_bf2.sig2_loaded = true;
|
||||
furi_string_set(subghz->keeloq_bf2.sig2_path, path);
|
||||
}
|
||||
furi_string_free(path);
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexType) {
|
||||
uint8_t cur = subghz->keeloq_bf2.learn_type;
|
||||
if(cur == 0) cur = 6;
|
||||
else if(cur == 6) cur = 7;
|
||||
else if(cur == 7) cur = 8;
|
||||
else cur = 0;
|
||||
subghz->keeloq_bf2.learn_type = cur;
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
|
||||
} else if(event.event == KlBf2IndexStartBf) {
|
||||
if(!subghz->keeloq_bf2.sig1_loaded || !subghz->keeloq_bf2.sig2_loaded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!subghz_key_load(
|
||||
subghz,
|
||||
furi_string_get_cstr(subghz->keeloq_bf2.sig1_path),
|
||||
true)) {
|
||||
dialog_message_show_storage_error(
|
||||
subghz->dialogs, "Cannot reload\nSignal 1");
|
||||
kl_bf2_rebuild_menu(subghz);
|
||||
return true;
|
||||
}
|
||||
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_bf2_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
#include "../subghz_i.h"
|
||||
#include "../helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/protocols/keeloq.h>
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
#include <lib/subghz/environment.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
#include <furi.h>
|
||||
#include <bt/bt_service/bt.h>
|
||||
|
||||
#define KL_DECRYPT_EVENT_DONE (0xD2)
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
#define KL_MSG_BF_REQUEST 0x10
|
||||
#define KL_MSG_BF_PROGRESS 0x11
|
||||
#define KL_MSG_BF_RESULT 0x12
|
||||
#define KL_MSG_BF_CANCEL 0x13
|
||||
|
||||
typedef struct {
|
||||
SubGhz* subghz;
|
||||
volatile bool cancel;
|
||||
uint32_t start_tick;
|
||||
bool success;
|
||||
FuriString* result;
|
||||
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
|
||||
uint32_t hop2;
|
||||
|
||||
uint32_t candidate_count;
|
||||
uint64_t recovered_mfkey;
|
||||
uint16_t recovered_type;
|
||||
uint32_t recovered_cnt;
|
||||
|
||||
bool ble_offload;
|
||||
} KlDecryptCtx;
|
||||
|
||||
static void kl_ble_data_received(uint8_t* data, uint16_t size, void* context) {
|
||||
KlDecryptCtx* ctx = context;
|
||||
if(size < 1 || ctx->cancel) return;
|
||||
|
||||
if(data[0] == KL_MSG_BF_PROGRESS && size >= 10) {
|
||||
uint32_t keys_tested, keys_per_sec;
|
||||
memcpy(&keys_tested, data + 2, 4);
|
||||
memcpy(&keys_per_sec, data + 6, 4);
|
||||
|
||||
uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000;
|
||||
uint32_t remaining = (keys_tested > 0) ? (0xFFFFFFFFU - keys_tested) : 0xFFFFFFFFU;
|
||||
uint32_t eta_sec = (keys_per_sec > 0) ? (remaining / keys_per_sec) : 0;
|
||||
uint8_t pct = (uint8_t)((uint64_t)keys_tested * 100 / 0xFFFFFFFFULL);
|
||||
|
||||
subghz_view_keeloq_decrypt_update_stats(
|
||||
ctx->subghz->subghz_keeloq_decrypt, pct, keys_tested, keys_per_sec, elapsed_sec, eta_sec);
|
||||
|
||||
} else if(data[0] == KL_MSG_BF_RESULT && size >= 26) {
|
||||
uint8_t found = data[1];
|
||||
uint64_t mfkey = 0;
|
||||
uint64_t devkey = 0;
|
||||
uint32_t cnt = 0;
|
||||
uint32_t elapsed_ms = 0;
|
||||
memcpy(&mfkey, data + 2, 8);
|
||||
memcpy(&devkey, data + 10, 8);
|
||||
memcpy(&cnt, data + 18, 4);
|
||||
memcpy(&elapsed_ms, data + 22, 4);
|
||||
|
||||
if(found == 1) {
|
||||
uint16_t learn_type = (size >= 27) ? data[26] : 6;
|
||||
|
||||
ctx->candidate_count++;
|
||||
ctx->recovered_mfkey = mfkey;
|
||||
ctx->recovered_type = learn_type;
|
||||
ctx->recovered_cnt = cnt;
|
||||
|
||||
subghz_view_keeloq_decrypt_update_candidates(
|
||||
ctx->subghz->subghz_keeloq_decrypt, ctx->candidate_count);
|
||||
|
||||
if(!ctx->subghz->keeloq_keys_manager) {
|
||||
ctx->subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
char key_name[24];
|
||||
snprintf(key_name, sizeof(key_name), "BF_%07lX", ctx->serial);
|
||||
subghz_keeloq_keys_add(
|
||||
ctx->subghz->keeloq_keys_manager,
|
||||
mfkey,
|
||||
learn_type,
|
||||
key_name);
|
||||
subghz_keeloq_keys_save(ctx->subghz->keeloq_keys_manager);
|
||||
|
||||
SubGhzKeystore* env_ks = subghz_environment_get_keystore(
|
||||
ctx->subghz->txrx->environment);
|
||||
SubGhzKeyArray_t* env_arr = subghz_keystore_get_data(env_ks);
|
||||
SubGhzKey* entry = SubGhzKeyArray_push_raw(*env_arr);
|
||||
entry->name = furi_string_alloc_set(key_name);
|
||||
entry->key = mfkey;
|
||||
entry->type = learn_type;
|
||||
|
||||
} else if(found == 2) {
|
||||
ctx->success = (ctx->candidate_count > 0);
|
||||
|
||||
if(ctx->candidate_count > 0) {
|
||||
furi_string_printf(
|
||||
ctx->result,
|
||||
"Found %lu candidate(s)\n"
|
||||
"Last: %08lX%08lX\n"
|
||||
"Type:%u Cnt:%04lX\n"
|
||||
"Saved to user keys",
|
||||
ctx->candidate_count,
|
||||
(uint32_t)(ctx->recovered_mfkey >> 32),
|
||||
(uint32_t)(ctx->recovered_mfkey & 0xFFFFFFFF),
|
||||
ctx->recovered_type,
|
||||
ctx->recovered_cnt);
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(ctx->subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
char mf_str[20];
|
||||
snprintf(mf_str, sizeof(mf_str), "BF_%07lX", ctx->serial);
|
||||
flipper_format_insert_or_update_string_cstr(fff, "Manufacture", mf_str);
|
||||
|
||||
uint32_t cnt_val = ctx->recovered_cnt;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &cnt_val, 1);
|
||||
|
||||
if(ctx->hop2 != 0) {
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
view_dispatcher_send_custom_event(ctx->subghz->view_dispatcher, KL_DECRYPT_EVENT_DONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void kl_ble_cleanup(KlDecryptCtx* ctx) {
|
||||
if(!ctx->ble_offload) return;
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
bt_set_custom_data_callback(bt, NULL, NULL);
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = false;
|
||||
}
|
||||
|
||||
static bool kl_ble_start_offload(KlDecryptCtx* ctx) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
if(!bt_is_connected(bt)) {
|
||||
furi_record_close(RECORD_BT);
|
||||
return false;
|
||||
}
|
||||
|
||||
bt_set_custom_data_callback(bt, kl_ble_data_received, ctx);
|
||||
|
||||
uint8_t req[18];
|
||||
req[0] = KL_MSG_BF_REQUEST;
|
||||
req[1] = ctx->subghz->keeloq_bf2.learn_type;
|
||||
memcpy(req + 2, &ctx->fix, 4);
|
||||
memcpy(req + 6, &ctx->hop, 4);
|
||||
memcpy(req + 10, &ctx->hop2, 4);
|
||||
memcpy(req + 14, &ctx->serial, 4);
|
||||
bt_custom_data_tx(bt, req, sizeof(req));
|
||||
|
||||
furi_record_close(RECORD_BT);
|
||||
ctx->ble_offload = true;
|
||||
|
||||
subghz_view_keeloq_decrypt_set_status(
|
||||
ctx->subghz->subghz_keeloq_decrypt, "[BT] Offloading...");
|
||||
return true;
|
||||
}
|
||||
|
||||
static void kl_decrypt_view_callback(SubGhzCustomEvent event, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlDecryptCtx* ctx = malloc(sizeof(KlDecryptCtx));
|
||||
memset(ctx, 0, sizeof(KlDecryptCtx));
|
||||
ctx->subghz = subghz;
|
||||
ctx->result = furi_string_alloc_set("No result");
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
}
|
||||
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
ctx->hop2 = subghz->keeloq_bf2.sig2_loaded ? subghz->keeloq_bf2.hop2 : 0;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
subghz_view_keeloq_decrypt_reset(subghz->subghz_keeloq_decrypt);
|
||||
subghz_view_keeloq_decrypt_set_callback(
|
||||
subghz->subghz_keeloq_decrypt, kl_decrypt_view_callback, subghz);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
|
||||
ctx->start_tick = furi_get_tick();
|
||||
|
||||
if(!kl_ble_start_offload(ctx)) {
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg),
|
||||
"No BLE connection!\n"
|
||||
"Connect companion app\n"
|
||||
"and try again.\n\n"
|
||||
"Fix:0x%08lX\nHop:0x%08lX",
|
||||
ctx->fix, ctx->hop);
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, msg);
|
||||
}
|
||||
}
|
||||
|
||||
bool subghz_scene_keeloq_decrypt_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
if(!ctx) return false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KL_DECRYPT_EVENT_DONE) {
|
||||
kl_ble_cleanup(ctx);
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
|
||||
if(ctx->success) {
|
||||
if(subghz_path_is_file(subghz->file_path)) {
|
||||
subghz_save_protocol_to_file(
|
||||
subghz,
|
||||
subghz_txrx_get_fff_data(subghz->txrx),
|
||||
furi_string_get_cstr(subghz->file_path));
|
||||
}
|
||||
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, true, furi_string_get_cstr(ctx->result));
|
||||
} else if(!ctx->cancel) {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false,
|
||||
"Key NOT found.\nNo matching key in\n2^32 search space.");
|
||||
} else {
|
||||
subghz_view_keeloq_decrypt_set_result(
|
||||
subghz->subghz_keeloq_decrypt, false, "Cancelled.");
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event.event == SubGhzCustomEventViewTransmitterBack) {
|
||||
if(ctx->ble_offload) {
|
||||
Bt* bt = furi_record_open(RECORD_BT);
|
||||
uint8_t cancel_msg = KL_MSG_BF_CANCEL;
|
||||
bt_custom_data_tx(bt, &cancel_msg, 1);
|
||||
furi_record_close(RECORD_BT);
|
||||
}
|
||||
ctx->cancel = true;
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_keeloq_decrypt_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
KlDecryptCtx* ctx = (KlDecryptCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKeeloqDecrypt);
|
||||
|
||||
if(ctx) {
|
||||
kl_ble_cleanup(ctx);
|
||||
ctx->cancel = true;
|
||||
furi_string_free(ctx->result);
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKeeloqDecrypt, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/protocols/keeloq_common.h>
|
||||
|
||||
typedef struct {
|
||||
uint32_t serial;
|
||||
uint32_t fix;
|
||||
uint32_t hop;
|
||||
uint32_t hop2;
|
||||
uint8_t btn;
|
||||
uint16_t disc;
|
||||
size_t bf_indices[32];
|
||||
size_t bf_count;
|
||||
size_t valid_indices[32];
|
||||
size_t valid_count;
|
||||
} KlCleanupCtx;
|
||||
|
||||
static bool kl_cleanup_validate_hop(uint64_t key, uint32_t hop, uint8_t btn, uint16_t disc) {
|
||||
uint32_t dec = subghz_protocol_keeloq_common_decrypt(hop, key);
|
||||
if((dec >> 28) != btn) return false;
|
||||
uint16_t dec_disc = (dec >> 16) & 0x3FF;
|
||||
if(dec_disc == disc) return true;
|
||||
if((dec_disc & 0xFF) == (disc & 0xFF)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool kl_cleanup_validate_key(uint64_t key, uint32_t hop1, uint32_t hop2, uint8_t btn, uint16_t disc) {
|
||||
if(!kl_cleanup_validate_hop(key, hop1, btn, disc)) return false;
|
||||
if(hop2 == 0) return true;
|
||||
if(!kl_cleanup_validate_hop(key, hop2, btn, disc)) return false;
|
||||
uint32_t dec1 = subghz_protocol_keeloq_common_decrypt(hop1, key);
|
||||
uint32_t dec2 = subghz_protocol_keeloq_common_decrypt(hop2, key);
|
||||
uint16_t cnt1 = dec1 & 0xFFFF;
|
||||
uint16_t cnt2 = dec2 & 0xFFFF;
|
||||
int diff = (int)cnt2 - (int)cnt1;
|
||||
return (diff >= 1 && diff <= 256);
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = malloc(sizeof(KlCleanupCtx));
|
||||
memset(ctx, 0, sizeof(KlCleanupCtx));
|
||||
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
flipper_format_rewind(fff);
|
||||
|
||||
uint8_t key_data[8] = {0};
|
||||
if(flipper_format_read_hex(fff, "Key", key_data, 8)) {
|
||||
ctx->fix = ((uint32_t)key_data[0] << 24) | ((uint32_t)key_data[1] << 16) |
|
||||
((uint32_t)key_data[2] << 8) | key_data[3];
|
||||
ctx->hop = ((uint32_t)key_data[4] << 24) | ((uint32_t)key_data[5] << 16) |
|
||||
((uint32_t)key_data[6] << 8) | key_data[7];
|
||||
ctx->serial = ctx->fix & 0x0FFFFFFF;
|
||||
ctx->btn = ctx->fix >> 28;
|
||||
ctx->disc = ctx->serial & 0x3FF;
|
||||
}
|
||||
|
||||
ctx->hop2 = 0;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Hop2", &ctx->hop2, 1);
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup, (uint32_t)(uintptr_t)ctx);
|
||||
|
||||
if(!subghz->keeloq_keys_manager) {
|
||||
subghz->keeloq_keys_manager = subghz_keeloq_keys_alloc();
|
||||
}
|
||||
|
||||
char bf_name[24];
|
||||
snprintf(bf_name, sizeof(bf_name), "BF_%07lX", ctx->serial);
|
||||
|
||||
size_t user_count = subghz_keeloq_keys_user_count(subghz->keeloq_keys_manager);
|
||||
ctx->bf_count = 0;
|
||||
ctx->valid_count = 0;
|
||||
|
||||
for(size_t i = 0; i < user_count && ctx->bf_count < 32; i++) {
|
||||
SubGhzKey* k = subghz_keeloq_keys_get(subghz->keeloq_keys_manager, i);
|
||||
if(!k || !k->name) continue;
|
||||
const char* name = furi_string_get_cstr(k->name);
|
||||
if(strcmp(name, bf_name) == 0) {
|
||||
ctx->bf_indices[ctx->bf_count] = i;
|
||||
if(kl_cleanup_validate_key(k->key, ctx->hop, ctx->hop2, ctx->btn, ctx->disc)) {
|
||||
ctx->valid_indices[ctx->valid_count++] = i;
|
||||
}
|
||||
ctx->bf_count++;
|
||||
}
|
||||
}
|
||||
|
||||
FuriString* msg = furi_string_alloc();
|
||||
|
||||
if(ctx->bf_count == 0) {
|
||||
furi_string_set_str(msg, "No BF candidate keys\nfound for this serial.");
|
||||
} else if(ctx->bf_count == 1) {
|
||||
furi_string_set_str(msg, "Only 1 BF key exists.\nNothing to clean up.");
|
||||
} else if(ctx->valid_count == 1) {
|
||||
size_t deleted = 0;
|
||||
for(int i = (int)ctx->bf_count - 1; i >= 0; i--) {
|
||||
if(ctx->bf_indices[i] != ctx->valid_indices[0]) {
|
||||
subghz_keeloq_keys_delete(subghz->keeloq_keys_manager, ctx->bf_indices[i]);
|
||||
deleted++;
|
||||
}
|
||||
}
|
||||
subghz_keeloq_keys_save(subghz->keeloq_keys_manager);
|
||||
|
||||
furi_string_printf(msg,
|
||||
"Cleaned %u keys.\nKept valid key:\n%s",
|
||||
deleted, bf_name);
|
||||
} else if(ctx->valid_count == 0) {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys found\nbut none validates\nhop. Kept all.",
|
||||
ctx->bf_count);
|
||||
} else {
|
||||
furi_string_printf(msg,
|
||||
"%u BF keys, %u valid.\nCannot auto-select.\nKept all.",
|
||||
ctx->bf_count, ctx->valid_count);
|
||||
}
|
||||
|
||||
widget_add_text_scroll_element(subghz->widget, 0, 0, 128, 64, furi_string_get_cstr(msg));
|
||||
furi_string_free(msg);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdWidget);
|
||||
}
|
||||
|
||||
bool subghz_scene_kl_bf_cleanup_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_kl_bf_cleanup_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
KlCleanupCtx* ctx = (KlCleanupCtx*)(uintptr_t)scene_manager_get_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneKlBfCleanup);
|
||||
if(ctx) {
|
||||
free(ctx);
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneKlBfCleanup, 0);
|
||||
}
|
||||
|
||||
widget_reset(subghz->widget);
|
||||
}
|
||||
@@ -6,8 +6,7 @@ enum SubmenuIndex {
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexPsaDecrypt,
|
||||
SubmenuIndexCounterBf,
|
||||
SubmenuIndexKlBfCleanup,
|
||||
SubmenuIndexCounterBf
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -18,6 +17,7 @@ void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// Check protocol type for conditional menu items
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
bool is_psa_encrypted = false;
|
||||
bool has_counter = false;
|
||||
@@ -26,6 +26,7 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Protocol", proto)) {
|
||||
if(furi_string_equal_str(proto, "PSA GROUP")) {
|
||||
// Check if Type field is missing or zero (not yet decrypted)
|
||||
FuriString* type_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(!flipper_format_read_string(fff, "Type", type_str) ||
|
||||
@@ -38,6 +39,7 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
furi_string_free(proto);
|
||||
}
|
||||
|
||||
// Check if protocol has a Cnt field (supports counter bruteforce)
|
||||
if(fff) {
|
||||
uint32_t cnt_tmp = 0;
|
||||
flipper_format_rewind(fff);
|
||||
@@ -46,18 +48,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
}
|
||||
}
|
||||
|
||||
bool has_bf_keys = false;
|
||||
if(fff) {
|
||||
FuriString* mfg = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Manufacture", mfg)) {
|
||||
if(furi_string_start_with_str(mfg, "BF_")) {
|
||||
has_bf_keys = true;
|
||||
}
|
||||
}
|
||||
furi_string_free(mfg);
|
||||
}
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Emulate",
|
||||
@@ -103,14 +93,6 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
if(has_bf_keys) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Clean BF Keys",
|
||||
SubmenuIndexKlBfCleanup,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
@@ -153,11 +135,6 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexCounterBf);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCounterBf);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexKlBfCleanup) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexKlBfCleanup);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKlBfCleanup);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -55,12 +55,6 @@ void subghz_scene_start_on_enter(void* context) {
|
||||
SubmenuIndexKeeloqKeys,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"KeeLoq BF (2 Signals)",
|
||||
SubmenuIndexKeeloqBf2,
|
||||
subghz_scene_start_submenu_callback,
|
||||
subghz);
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
|
||||
|
||||
@@ -118,11 +112,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqKeys);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqKeys);
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexKeeloqBf2) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneStart, SubmenuIndexKeeloqBf2);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneKeeloqBf2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -10,5 +10,4 @@ enum SubmenuIndex {
|
||||
SubmenuIndexProtocolList,
|
||||
SubmenuIndexRadioSetting,
|
||||
SubmenuIndexKeeloqKeys,
|
||||
SubmenuIndexKeeloqBf2,
|
||||
};
|
||||
|
||||
@@ -95,11 +95,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
|
||||
subghz->keeloq_bf2.sig1_loaded = false;
|
||||
subghz->keeloq_bf2.sig2_loaded = false;
|
||||
subghz->keeloq_bf2.sig1_path = furi_string_alloc();
|
||||
subghz->keeloq_bf2.sig2_path = furi_string_alloc();
|
||||
|
||||
subghz->file_path = furi_string_alloc();
|
||||
subghz->file_path_tmp = furi_string_alloc();
|
||||
|
||||
@@ -200,12 +195,6 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
subghz_view_psa_decrypt_get_view(subghz->subghz_psa_decrypt));
|
||||
|
||||
subghz->subghz_keeloq_decrypt = subghz_view_keeloq_decrypt_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
|
||||
|
||||
//init threshold rssi
|
||||
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
|
||||
|
||||
@@ -317,10 +306,6 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdPsaDecrypt);
|
||||
subghz_view_psa_decrypt_free(subghz->subghz_psa_decrypt);
|
||||
|
||||
// KeeLoq Decrypt
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
|
||||
|
||||
// Read RAW
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
||||
@@ -368,9 +353,7 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
furi_string_free(subghz->file_path);
|
||||
furi_string_free(subghz->file_path_tmp);
|
||||
|
||||
furi_string_free(subghz->keeloq_bf2.sig1_path);
|
||||
furi_string_free(subghz->keeloq_bf2.sig2_path);
|
||||
|
||||
// KeeLoq key manager (may still be live if app exited from within the edit scene)
|
||||
if(subghz->keeloq_keys_manager) {
|
||||
subghz_keeloq_keys_free(subghz->keeloq_keys_manager);
|
||||
subghz->keeloq_keys_manager = NULL;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "views/subghz_frequency_analyzer.h"
|
||||
#include "views/subghz_read_raw.h"
|
||||
#include "views/subghz_psa_decrypt.h"
|
||||
#include "views/subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <assets_icons.h>
|
||||
@@ -75,7 +74,6 @@ struct SubGhz {
|
||||
SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
|
||||
SubGhzReadRAW* subghz_read_raw;
|
||||
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
||||
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
|
||||
bool raw_send_only;
|
||||
|
||||
bool save_datetime_set;
|
||||
@@ -104,25 +102,13 @@ struct SubGhz {
|
||||
// KeeLoq key management
|
||||
SubGhzKeeloqKeysManager* keeloq_keys_manager;
|
||||
struct {
|
||||
uint8_t key_bytes[8];
|
||||
char name[65];
|
||||
uint16_t type;
|
||||
bool is_new;
|
||||
size_t edit_index;
|
||||
uint8_t edit_step;
|
||||
uint8_t key_bytes[8]; // ByteInput result
|
||||
char name[65]; // TextInput result
|
||||
uint16_t type; // selected learning type 1..8
|
||||
bool is_new; // true = add, false = edit
|
||||
size_t edit_index; // valid when is_new == false
|
||||
uint8_t edit_step; // 0 = key, 1 = name, 2 = type
|
||||
} keeloq_edit;
|
||||
|
||||
struct {
|
||||
uint32_t fix;
|
||||
uint32_t hop1;
|
||||
uint32_t hop2;
|
||||
uint32_t serial;
|
||||
bool sig1_loaded;
|
||||
bool sig2_loaded;
|
||||
FuriString* sig1_path;
|
||||
FuriString* sig2_path;
|
||||
uint8_t learn_type;
|
||||
} keeloq_bf2;
|
||||
};
|
||||
|
||||
void subghz_blink_start(SubGhz* subghz);
|
||||
|
||||
@@ -1,246 +0,0 @@
|
||||
#include "subghz_keeloq_decrypt.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define KL_TOTAL_KEYS 0x100000000ULL
|
||||
|
||||
struct SubGhzViewKeeloqDecrypt {
|
||||
View* view;
|
||||
SubGhzViewKeeloqDecryptCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t progress;
|
||||
uint32_t keys_tested;
|
||||
uint32_t keys_per_sec;
|
||||
uint32_t elapsed_sec;
|
||||
uint32_t eta_sec;
|
||||
bool done;
|
||||
bool success;
|
||||
uint32_t candidates;
|
||||
FuriString* result_str;
|
||||
char status_line[40];
|
||||
} SubGhzKeeloqDecryptModel;
|
||||
|
||||
static void subghz_view_keeloq_decrypt_format_count(char* buf, size_t len, uint32_t count) {
|
||||
if(count >= 1000000) {
|
||||
snprintf(buf, len, "%lu.%luM", count / 1000000, (count % 1000000) / 100000);
|
||||
} else if(count >= 1000) {
|
||||
snprintf(buf, len, "%luK", count / 1000);
|
||||
} else {
|
||||
snprintf(buf, len, "%lu", count);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_view_keeloq_decrypt_draw(Canvas* canvas, void* _model) {
|
||||
SubGhzKeeloqDecryptModel* model = (SubGhzKeeloqDecryptModel*)_model;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
if(!model->done) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
if(model->status_line[0]) {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, model->status_line);
|
||||
} else {
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "KeeLoq BF");
|
||||
}
|
||||
|
||||
canvas_draw_rframe(canvas, 3, 15, 122, 12, 2);
|
||||
uint8_t fill = (uint8_t)((uint16_t)model->progress * 116 / 100);
|
||||
if(fill > 0) {
|
||||
canvas_draw_rbox(canvas, 5, 17, fill, 8, 1);
|
||||
}
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
|
||||
char keys_str[32];
|
||||
char tested_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(tested_buf, sizeof(tested_buf), model->keys_tested);
|
||||
snprintf(keys_str, sizeof(keys_str), "%d%% - %s / 4G keys", model->progress, tested_buf);
|
||||
canvas_draw_str(canvas, 2, 38, keys_str);
|
||||
|
||||
char speed_str[40];
|
||||
char speed_buf[12];
|
||||
subghz_view_keeloq_decrypt_format_count(speed_buf, sizeof(speed_buf), model->keys_per_sec);
|
||||
uint32_t eta_m = model->eta_sec / 60;
|
||||
uint32_t eta_s = model->eta_sec % 60;
|
||||
if(eta_m > 0) {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lum %lus", speed_buf, eta_m, eta_s);
|
||||
} else {
|
||||
snprintf(speed_str, sizeof(speed_str), "%s keys/sec ETA %lus", speed_buf, eta_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 48, speed_str);
|
||||
|
||||
if(model->candidates > 0) {
|
||||
char cand_str[32];
|
||||
snprintf(cand_str, sizeof(cand_str), "Candidates: %lu", model->candidates);
|
||||
canvas_draw_str(canvas, 2, 58, cand_str);
|
||||
} else {
|
||||
char elapsed_str[24];
|
||||
uint32_t el_m = model->elapsed_sec / 60;
|
||||
uint32_t el_s = model->elapsed_sec % 60;
|
||||
if(el_m > 0) {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lum %lus", el_m, el_s);
|
||||
} else {
|
||||
snprintf(elapsed_str, sizeof(elapsed_str), "Elapsed: %lus", el_s);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 58, elapsed_str);
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 126, 64, AlignRight, AlignBottom, "Hold BACK");
|
||||
} else {
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
if(model->result_str) {
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 0, 0, AlignLeft, AlignTop, furi_string_get_cstr(model->result_str));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool subghz_view_keeloq_decrypt_input(InputEvent* event, void* context) {
|
||||
SubGhzViewKeeloqDecrypt* instance = (SubGhzViewKeeloqDecrypt*)context;
|
||||
|
||||
if(event->key == InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventViewTransmitterBack, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void) {
|
||||
SubGhzViewKeeloqDecrypt* instance = malloc(sizeof(SubGhzViewKeeloqDecrypt));
|
||||
instance->view = view_alloc();
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzKeeloqDecryptModel));
|
||||
view_set_context(instance->view, instance);
|
||||
view_set_draw_callback(instance->view, subghz_view_keeloq_decrypt_draw);
|
||||
view_set_input_callback(instance->view, subghz_view_keeloq_decrypt_input);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->result_str = furi_string_alloc();
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
},
|
||||
false);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ furi_string_free(model->result_str); },
|
||||
false);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = progress;
|
||||
model->keys_tested = keys_tested;
|
||||
model->keys_per_sec = keys_per_sec;
|
||||
model->elapsed_sec = elapsed_sec;
|
||||
model->eta_sec = eta_sec;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->done = true;
|
||||
model->success = success;
|
||||
furi_string_set_str(model->result_str, result);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
model->progress = 0;
|
||||
model->keys_tested = 0;
|
||||
model->keys_per_sec = 0;
|
||||
model->elapsed_sec = 0;
|
||||
model->eta_sec = 0;
|
||||
model->done = false;
|
||||
model->success = false;
|
||||
model->candidates = 0;
|
||||
furi_string_reset(model->result_str);
|
||||
model->status_line[0] = '\0';
|
||||
},
|
||||
false);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{
|
||||
if(status) {
|
||||
strlcpy(model->status_line, status, sizeof(model->status_line));
|
||||
} else {
|
||||
model->status_line[0] = '\0';
|
||||
}
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzKeeloqDecryptModel * model,
|
||||
{ model->candidates = count; },
|
||||
true);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
typedef struct SubGhzViewKeeloqDecrypt SubGhzViewKeeloqDecrypt;
|
||||
|
||||
typedef void (*SubGhzViewKeeloqDecryptCallback)(SubGhzCustomEvent event, void* context);
|
||||
|
||||
SubGhzViewKeeloqDecrypt* subghz_view_keeloq_decrypt_alloc(void);
|
||||
void subghz_view_keeloq_decrypt_free(SubGhzViewKeeloqDecrypt* instance);
|
||||
View* subghz_view_keeloq_decrypt_get_view(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_callback(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
SubGhzViewKeeloqDecryptCallback callback,
|
||||
void* context);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_stats(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
uint8_t progress,
|
||||
uint32_t keys_tested,
|
||||
uint32_t keys_per_sec,
|
||||
uint32_t elapsed_sec,
|
||||
uint32_t eta_sec);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_result(
|
||||
SubGhzViewKeeloqDecrypt* instance,
|
||||
bool success,
|
||||
const char* result);
|
||||
|
||||
void subghz_view_keeloq_decrypt_reset(SubGhzViewKeeloqDecrypt* instance);
|
||||
|
||||
void subghz_view_keeloq_decrypt_set_status(SubGhzViewKeeloqDecrypt* instance, const char* status);
|
||||
|
||||
void subghz_view_keeloq_decrypt_update_candidates(
|
||||
SubGhzViewKeeloqDecrypt* instance, uint32_t count);
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 511 B |
|
Before Width: | Height: | Size: 968 B After Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 483 B |
296
lib/subghz/protocols/bmw.c
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "bmw.h"
|
||||
|
||||
#define TAG "SubGhzProtocolBMW_868"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_bmw_const = {
|
||||
.te_short = 350, // BMW 868 MHz
|
||||
.te_long = 700, // ~2 × te_short
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 61,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderBMW {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
uint8_t crc_type; // 0 = unknown, 8 = CRC8, 16 = CRC16
|
||||
} SubGhzProtocolDecoderBMW;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderBMW {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} SubGhzProtocolEncoderBMW;
|
||||
|
||||
typedef enum {
|
||||
BMWDecoderStepReset = 0,
|
||||
BMWDecoderStepCheckPreambula,
|
||||
BMWDecoderStepSaveDuration,
|
||||
BMWDecoderStepCheckDuration,
|
||||
} BMWDecoderStep;
|
||||
|
||||
static void subghz_protocol_decoder_bmw_reset_internal(SubGhzProtocolDecoderBMW* instance) {
|
||||
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->crc_type = 0;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_bmw_decoder = {
|
||||
.alloc = subghz_protocol_decoder_bmw_alloc,
|
||||
.free = subghz_protocol_decoder_bmw_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_bmw_feed,
|
||||
.reset = subghz_protocol_decoder_bmw_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_bmw_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_bmw_serialize,
|
||||
.deserialize = subghz_protocol_decoder_bmw_deserialize,
|
||||
.get_string = subghz_protocol_decoder_bmw_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_bmw_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_bmw = {
|
||||
.name = BMW_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_bmw_decoder,
|
||||
.encoder = &subghz_protocol_bmw_encoder,
|
||||
};
|
||||
|
||||
// ----------------- Allocation / Reset / Free -------------------
|
||||
|
||||
void* subghz_protocol_decoder_bmw_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderBMW* instance = calloc(1, sizeof(SubGhzProtocolDecoderBMW));
|
||||
instance->base.protocol = &subghz_protocol_bmw;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_bmw_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
subghz_protocol_decoder_bmw_reset_internal(instance);
|
||||
}
|
||||
|
||||
// ----------------- CRC -------------------
|
||||
// BMW utilise CRC-8 (poly 0x31, init 0x00)
|
||||
|
||||
uint8_t subghz_protocol_bmw_crc8(uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x00;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for(uint8_t j = 0; j < 8; j++) {
|
||||
if(crc & 0x80)
|
||||
crc = (uint8_t)((crc << 1) ^ 0x31);
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
// BMW utilise aussi CRC-16 (poly 0x1021, init 0xFFFF)
|
||||
uint16_t subghz_protocol_bmw_crc16(uint8_t* data, size_t len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= ((uint16_t)data[i] << 8);
|
||||
for(uint8_t j = 0; j < 8; j++) {
|
||||
if(crc & 0x8000)
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
// ----------------- Decoder Feed -------------------
|
||||
|
||||
void subghz_protocol_decoder_bmw_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case BMWDecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
instance->decoder.parser_step = BMWDecoderStepCheckPreambula;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case BMWDecoderStepCheckPreambula:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta) ||
|
||||
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||
subghz_protocol_bmw_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_long) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
if(instance->header_count > 15) {
|
||||
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0ULL;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case BMWDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(duration >=
|
||||
(subghz_protocol_bmw_const.te_long + subghz_protocol_bmw_const.te_delta * 2UL)) {
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_bmw_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
// Perform CRC check with both CRC8 and CRC16
|
||||
uint8_t* raw_bytes = (uint8_t*)&instance->generic.data;
|
||||
size_t raw_len = (instance->generic.data_count_bit + 7) / 8;
|
||||
uint8_t crc8 = subghz_protocol_bmw_crc8(raw_bytes, raw_len - 1);
|
||||
if(crc8 == raw_bytes[raw_len - 1]) {
|
||||
instance->crc_type = 8;
|
||||
} else {
|
||||
uint16_t crc16 = subghz_protocol_bmw_crc16(raw_bytes, raw_len - 2);
|
||||
uint16_t rx_crc16 = (raw_bytes[raw_len - 2] << 8) | raw_bytes[raw_len - 1];
|
||||
if(crc16 == rx_crc16) {
|
||||
instance->crc_type = 16;
|
||||
} else {
|
||||
instance->crc_type = 0; // invalid
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->crc_type != 0 && instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
subghz_protocol_decoder_bmw_reset_internal(instance);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = BMWDecoderStepCheckDuration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case BMWDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_long) <
|
||||
subghz_protocol_bmw_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||
subghz_protocol_bmw_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- Utils -------------------
|
||||
|
||||
static void subghz_protocol_bmw_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF);
|
||||
instance->btn = (instance->data >> 8) & 0x0F;
|
||||
instance->cnt = (instance->data >> 40) & 0xFFFF;
|
||||
}
|
||||
|
||||
// ----------------- API -------------------
|
||||
|
||||
uint8_t subghz_protocol_decoder_bmw_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_bmw_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderBMW* instance = context;
|
||||
|
||||
subghz_protocol_bmw_check_remote_controller(&instance->generic);
|
||||
uint32_t hi = instance->generic.data >> 32;
|
||||
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit (CRC:%d)\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X Cnt:%04lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->crc_type,
|
||||
hi,
|
||||
lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt);
|
||||
}
|
||||
29
lib/subghz/protocols/bmw.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define BMW_PROTOCOL_NAME "BMW"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_bmw;
|
||||
|
||||
void* subghz_protocol_decoder_bmw_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_bmw_free(void* context);
|
||||
void subghz_protocol_decoder_bmw_reset(void* context);
|
||||
void subghz_protocol_decoder_bmw_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_bmw_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_bmw_get_string(void* context, FuriString* output);
|
||||
281
lib/subghz/protocols/citroen.c
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "citroen.h"
|
||||
|
||||
#define TAG "SubGhzProtocolCitroen"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_citroen_const = {
|
||||
.te_short = 370, // Short pulse duration
|
||||
.te_long = 772, // Long pulse duration
|
||||
.te_delta = 152, // Tolerance
|
||||
.min_count_bit_for_found = 66,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderCitroen {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
uint8_t packet_count;
|
||||
} SubGhzProtocolDecoderCitroen;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderCitroen {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} SubGhzProtocolEncoderCitroen;
|
||||
|
||||
typedef enum {
|
||||
CitroenDecoderStepReset = 0,
|
||||
CitroenDecoderStepCheckPreamble,
|
||||
CitroenDecoderStepSaveDuration,
|
||||
CitroenDecoderStepCheckDuration,
|
||||
} CitroenDecoderStep;
|
||||
|
||||
static void subghz_protocol_decoder_citroen_reset_internal(SubGhzProtocolDecoderCitroen* instance) {
|
||||
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->packet_count = 0;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_citroen_decoder = {
|
||||
.alloc = subghz_protocol_decoder_citroen_alloc,
|
||||
.free = subghz_protocol_decoder_citroen_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_citroen_feed,
|
||||
.reset = subghz_protocol_decoder_citroen_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_citroen_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_citroen_serialize,
|
||||
.deserialize = subghz_protocol_decoder_citroen_deserialize,
|
||||
.get_string = subghz_protocol_decoder_citroen_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_citroen_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_citroen = {
|
||||
.name = CITROEN_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_citroen_decoder,
|
||||
.encoder = &subghz_protocol_citroen_encoder,
|
||||
};
|
||||
|
||||
// ----------------- Allocation / Reset / Free -------------------
|
||||
|
||||
void* subghz_protocol_decoder_citroen_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderCitroen* instance = calloc(1, sizeof(SubGhzProtocolDecoderCitroen));
|
||||
instance->base.protocol = &subghz_protocol_citroen;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_citroen_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_citroen_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_citroen_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
subghz_protocol_decoder_citroen_reset_internal(instance);
|
||||
}
|
||||
|
||||
// ----------------- Helper Functions -------------------
|
||||
|
||||
static uint8_t reverse8(uint8_t byte) {
|
||||
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||
return byte;
|
||||
}
|
||||
|
||||
// Parse Citroën/PSA data structure
|
||||
static bool subghz_protocol_citroen_parse_data(SubGhzProtocolDecoderCitroen* instance) {
|
||||
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||
|
||||
// PSA structure (similar to Peugeot Keeloq)
|
||||
// Check preamble
|
||||
if(b[0] != 0xFF || (b[1] & 0xF0) != 0xF0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract encrypted part (32 bits) - reversed
|
||||
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) |
|
||||
(reverse8(b[2]) << 16) |
|
||||
(reverse8(b[1] & 0x0F) << 8) |
|
||||
reverse8(b[0]);
|
||||
|
||||
// Extract serial number (28 bits) - reversed
|
||||
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 20) |
|
||||
(reverse8(b[6]) << 12) |
|
||||
(reverse8(b[5]) << 4) |
|
||||
(reverse8(b[4]) >> 4);
|
||||
|
||||
// Extract button bits (4 bits)
|
||||
uint8_t button_bits = (encrypted >> 28) & 0x0F;
|
||||
|
||||
// Store parsed data
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = button_bits;
|
||||
instance->generic.cnt = (encrypted >> 16) & 0xFFFF; // Counter
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------- Decoder Feed -------------------
|
||||
|
||||
void subghz_protocol_decoder_citroen_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case CitroenDecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta)) {
|
||||
instance->decoder.parser_step = CitroenDecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case CitroenDecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if((DURATION_DIFF(duration, 4400) < 500) && instance->header_count >= 10) {
|
||||
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0ULL;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CitroenDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(duration >= (subghz_protocol_citroen_const.te_long * 3)) {
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_citroen_const.min_count_bit_for_found) {
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
if(subghz_protocol_citroen_parse_data(instance)) {
|
||||
instance->packet_count++;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
subghz_protocol_decoder_citroen_reset_internal(instance);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = CitroenDecoderStepCheckDuration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case CitroenDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// PWM decoding
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_citroen_const.te_long) <
|
||||
subghz_protocol_citroen_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_long) <
|
||||
subghz_protocol_citroen_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||
subghz_protocol_citroen_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- API -------------------
|
||||
|
||||
uint8_t subghz_protocol_decoder_citroen_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_citroen_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_citroen_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_citroen_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_citroen_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderCitroen* instance = context;
|
||||
|
||||
uint32_t hi = instance->generic.data >> 32;
|
||||
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
|
||||
"Type:PSA/Keeloq\r\n"
|
||||
"Models:2005-2018\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
hi,
|
||||
lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt);
|
||||
}
|
||||
77
lib/subghz/protocols/citroen.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define CITROEN_PROTOCOL_NAME "Citroen"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_citroen;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_citroen_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_citroen_encoder;
|
||||
|
||||
/**
|
||||
* Allocates memory for the Citroën protocol decoder.
|
||||
* @param environment Pointer to SubGhzEnvironment
|
||||
* @return Pointer to the allocated decoder instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_citroen_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Frees memory used by the Citroën protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_citroen_free(void* context);
|
||||
|
||||
/**
|
||||
* Resets the Citroën protocol decoder state.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_citroen_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feeds a pulse/gap into the Citroën protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param level Signal level (true = high, false = low)
|
||||
* @param duration Duration of the level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_citroen_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Returns a hash of the decoded Citroën data.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_citroen_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serializes the decoded Citroën data into a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @param preset Pointer to the radio preset
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_citroen_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserializes Citroën data from a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_citroen_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Formats the decoded Citroën data into a human-readable string.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param output Pointer to the FuriString output buffer
|
||||
*/
|
||||
void subghz_protocol_decoder_citroen_get_string(void* context, FuriString* output);
|
||||
@@ -1,14 +1,6 @@
|
||||
#include "fiat_marelli.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"
|
||||
#include <inttypes.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <furi_hal_subghz.h>
|
||||
|
||||
#define TAG "FiatMarelli"
|
||||
|
||||
@@ -526,6 +518,28 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
const char* variant = (instance->te_detected &&
|
||||
instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY)
|
||||
? "B"
|
||||
: "A";
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Type%s TE:%lu %db Sn:%08lX Btn:0x%X Ep:%X Ctr:%lu Roll:%02X%02X%02X%02X%02X%02X",
|
||||
variant,
|
||||
instance->te_detected ? instance->te_detected : te_short,
|
||||
instance->bit_count,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->raw_data[6] & 0xF,
|
||||
instance->generic.cnt,
|
||||
instance->raw_data[7],
|
||||
instance->raw_data[8],
|
||||
instance->raw_data[9],
|
||||
instance->raw_data[10],
|
||||
instance->raw_data[11],
|
||||
instance->raw_data[12]);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
@@ -619,35 +633,34 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
|
||||
uint8_t total_bytes = (instance->bit_count + 7) / 8;
|
||||
if(total_bytes > 13) total_bytes = 13;
|
||||
|
||||
uint8_t epoch = instance->raw_data[6] & 0xF;
|
||||
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
const char* variant = (instance->te_detected &&
|
||||
instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY)
|
||||
? "B"
|
||||
: "A";
|
||||
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
|
||||
uint8_t fixed = instance->raw_data[7] & 0x1;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
|
||||
"Raw:%02X%02X Fixed:%X\r\n"
|
||||
"Sn:%08X Cnt:%02X\r\n"
|
||||
"Btn:%02X:[%s] Ep:%02X\r\n"
|
||||
"Tp:%s\r\n",
|
||||
"Sn:%08lX Btn:%s\r\n"
|
||||
"Ep:%X Ctr:%02d Type%s\r\n"
|
||||
"R:%02X%02X%02X%02X%02X%02X",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->bit_count,
|
||||
instance->raw_data[8], instance->raw_data[9],
|
||||
instance->raw_data[10], instance->raw_data[11],
|
||||
instance->raw_data[12],
|
||||
(unsigned)scramble,
|
||||
instance->raw_data[6], instance->raw_data[7],
|
||||
(unsigned)fixed,
|
||||
(unsigned int)instance->generic.serial,
|
||||
(unsigned)counter,
|
||||
(unsigned)instance->generic.btn,
|
||||
instance->bit_count,
|
||||
instance->generic.serial,
|
||||
fiat_marelli_button_name(instance->generic.btn),
|
||||
(unsigned)epoch,
|
||||
variant);
|
||||
epoch,
|
||||
counter,
|
||||
variant,
|
||||
instance->raw_data[7],
|
||||
instance->raw_data[8],
|
||||
instance->raw_data[9],
|
||||
(total_bytes > 10) ? instance->raw_data[10] : 0,
|
||||
(total_bytes > 11) ? instance->raw_data[11] : 0,
|
||||
(total_bytes > 12) ? instance->raw_data[12] : 0);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli"
|
||||
@@ -23,6 +31,7 @@ SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder (replay of captured frames)
|
||||
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_fiat_marelli_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
|
||||
274
lib/subghz/protocols/honda.c
Normal file
@@ -0,0 +1,274 @@
|
||||
#include "honda.h"
|
||||
|
||||
#define TAG "SubGhzProtocolHonda"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_honda_const = {
|
||||
.te_short = 432, // Short pulse ~432µs
|
||||
.te_long = 864, // Long pulse ~864µs (2x short)
|
||||
.te_delta = 150, // Tolerance
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHonda {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
} SubGhzProtocolDecoderHonda;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderHonda {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} SubGhzProtocolEncoderHonda;
|
||||
|
||||
typedef enum {
|
||||
HondaDecoderStepReset = 0,
|
||||
HondaDecoderStepCheckPreamble,
|
||||
HondaDecoderStepSaveDuration,
|
||||
HondaDecoderStepCheckDuration,
|
||||
} HondaDecoderStep;
|
||||
|
||||
static void subghz_protocol_decoder_honda_reset_internal(SubGhzProtocolDecoderHonda* instance) {
|
||||
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_alloc,
|
||||
.free = subghz_protocol_decoder_honda_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_honda_feed,
|
||||
.reset = subghz_protocol_decoder_honda_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_honda_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_honda_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_deserialize,
|
||||
.get_string = subghz_protocol_decoder_honda_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_honda = {
|
||||
.name = HONDA_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic, // Rolling code (vulnerable)
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_honda_decoder,
|
||||
.encoder = &subghz_protocol_honda_encoder,
|
||||
};
|
||||
|
||||
// ----------------- Allocation / Reset / Free -------------------
|
||||
|
||||
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHonda* instance = calloc(1, sizeof(SubGhzProtocolDecoderHonda));
|
||||
instance->base.protocol = &subghz_protocol_honda;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_honda_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
subghz_protocol_decoder_honda_reset_internal(instance);
|
||||
}
|
||||
|
||||
// ----------------- Honda Protocol Parsing -------------------
|
||||
|
||||
static bool subghz_protocol_honda_parse_data(SubGhzProtocolDecoderHonda* instance) {
|
||||
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||
|
||||
// Honda protocol structure (from rtl_433):
|
||||
// Bits 0-7: Preamble/sync
|
||||
// Bits 8-39: Device ID (32 bits)
|
||||
// Bits 40-55: Rolling counter (16 bits)
|
||||
// Bits 56-63: Function code (8 bits) - which button was pressed
|
||||
|
||||
// Extract device ID (bytes 1-4)
|
||||
uint32_t device_id = ((uint32_t)b[1] << 24) |
|
||||
(b[2] << 16) |
|
||||
(b[3] << 8) |
|
||||
b[4];
|
||||
|
||||
// Extract rolling counter (bytes 5-6)
|
||||
uint16_t rolling_counter = (b[5] << 8) | b[6];
|
||||
|
||||
// Extract function code (byte 7)
|
||||
uint8_t function = b[7];
|
||||
|
||||
// Store parsed data
|
||||
instance->generic.serial = device_id;
|
||||
instance->generic.cnt = rolling_counter;
|
||||
instance->generic.btn = function;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------- Decoder Feed -------------------
|
||||
|
||||
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case HondaDecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta)) {
|
||||
instance->decoder.parser_step = HondaDecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaDecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
// Looking for preamble pattern
|
||||
if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_long) <
|
||||
subghz_protocol_honda_const.te_delta * 2) &&
|
||||
instance->header_count >= 10) {
|
||||
// Long gap after preamble - start of data
|
||||
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0ULL;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(duration >= (subghz_protocol_honda_const.te_long * 3)) {
|
||||
// End of transmission
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_honda_const.min_count_bit_for_found) {
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
// Parse Honda protocol structure
|
||||
if(subghz_protocol_honda_parse_data(instance)) {
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
subghz_protocol_decoder_honda_reset_internal(instance);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = HondaDecoderStepCheckDuration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Manchester decoding (differential)
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_honda_const.te_long) <
|
||||
subghz_protocol_honda_const.te_delta)) {
|
||||
// Short-Long = 0
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_long) <
|
||||
subghz_protocol_honda_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||
subghz_protocol_honda_const.te_delta)) {
|
||||
// Long-Short = 1
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- API -------------------
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_honda_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderHonda* instance = context;
|
||||
|
||||
uint32_t hi = instance->generic.data >> 32;
|
||||
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"ID:%08lX Btn:%02X Cnt:%04X\r\n"
|
||||
"CVE:CVE-2022-27254\r\n"
|
||||
"Note:Rolling code vulnerable\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
hi,
|
||||
lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)instance->generic.cnt);
|
||||
}
|
||||
77
lib/subghz/protocols/honda.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define HONDA_PROTOCOL_NAME "Honda"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_honda;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
|
||||
|
||||
/**
|
||||
* Allocates memory for the Honda protocol decoder.
|
||||
* @param environment Pointer to SubGhzEnvironment
|
||||
* @return Pointer to the allocated decoder instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Frees memory used by the Honda protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_honda_free(void* context);
|
||||
|
||||
/**
|
||||
* Resets the Honda protocol decoder state.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_honda_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feeds a pulse/gap into the Honda protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param level Signal level (true = high, false = low)
|
||||
* @param duration Duration of the level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Returns a hash of the decoded Honda data.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serializes the decoded Honda data into a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @param preset Pointer to the radio preset
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserializes Honda data from a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Formats the decoded Honda data into a human-readable string.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param output Pointer to the FuriString output buffer
|
||||
*/
|
||||
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output);
|
||||
259
lib/subghz/protocols/mitsubishi_v1.c
Normal file
@@ -0,0 +1,259 @@
|
||||
#include "mitsubishi_v1.h"
|
||||
|
||||
#define TAG "SubGhzProtocolMitsubishi"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mitsubishi_const = {
|
||||
.te_short = 320, // Similar to KIA timing
|
||||
.te_long = 640, // ~2× te_short
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMitsubishi {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
} SubGhzProtocolDecoderMitsubishi;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderMitsubishi {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} SubGhzProtocolEncoderMitsubishi;
|
||||
|
||||
typedef enum {
|
||||
MitsubishiDecoderStepReset = 0,
|
||||
MitsubishiDecoderStepCheckPreamble,
|
||||
MitsubishiDecoderStepSaveDuration,
|
||||
MitsubishiDecoderStepCheckDuration,
|
||||
} MitsubishiDecoderStep;
|
||||
|
||||
static void subghz_protocol_decoder_mitsubishi_reset_internal(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mitsubishi_alloc,
|
||||
.free = subghz_protocol_decoder_mitsubishi_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_mitsubishi_feed,
|
||||
.reset = subghz_protocol_decoder_mitsubishi_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_mitsubishi_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_mitsubishi_serialize,
|
||||
.deserialize = subghz_protocol_decoder_mitsubishi_deserialize,
|
||||
.get_string = subghz_protocol_decoder_mitsubishi_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_mitsubishi_v1 = {
|
||||
.name = MITSUBISHI_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_mitsubishi_decoder,
|
||||
.encoder = &subghz_protocol_mitsubishi_encoder,
|
||||
};
|
||||
|
||||
// ----------------- Allocation / Reset / Free -------------------
|
||||
|
||||
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi));
|
||||
instance->base.protocol = &subghz_protocol_mitsubishi_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_mitsubishi_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
subghz_protocol_decoder_mitsubishi_reset_internal(instance);
|
||||
}
|
||||
|
||||
// ----------------- Helper Functions -------------------
|
||||
|
||||
// Parse Mitsubishi/KIA-Hyundai data structure
|
||||
static void subghz_protocol_mitsubishi_parse_data(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
// Structure similar to KIA/Hyundai protocol
|
||||
// Serial number in upper bits
|
||||
// Button code in middle bits
|
||||
// Counter in lower bits
|
||||
|
||||
instance->generic.serial = (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF);
|
||||
instance->generic.btn = (instance->generic.data >> 24) & 0xFF;
|
||||
instance->generic.cnt = (instance->generic.data >> 8) & 0xFFFF;
|
||||
}
|
||||
|
||||
// ----------------- Decoder Feed -------------------
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case MitsubishiDecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta) ||
|
||||
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
if(instance->header_count > 10) {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0ULL;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(duration >= (subghz_protocol_mitsubishi_const.te_long * 3)) {
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_mitsubishi_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
// Parse Mitsubishi data
|
||||
subghz_protocol_mitsubishi_parse_data(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
subghz_protocol_decoder_mitsubishi_reset_internal(instance);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepCheckDuration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Manchester-like decoding (KIA/Hyundai style)
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- API -------------------
|
||||
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mitsubishi_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
|
||||
uint32_t hi = instance->generic.data >> 32;
|
||||
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%08lX Btn:%02X Cnt:%04lX\r\n"
|
||||
"Type:KIA/Hyundai based\r\n"
|
||||
"Models:L200,Pajero,ASX+\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
hi,
|
||||
lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt);
|
||||
}
|
||||
77
lib/subghz/protocols/mitsubishi_v1.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define MITSUBISHI_PROTOCOL_NAME "Mitsubishi v1"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_mitsubishi_v1;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_mitsubishi_encoder;
|
||||
|
||||
/**
|
||||
* Allocates memory for the Mitsubishi protocol decoder.
|
||||
* @param environment Pointer to SubGhzEnvironment
|
||||
* @return Pointer to the allocated decoder instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Frees memory used by the Mitsubishi protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_mitsubishi_free(void* context);
|
||||
|
||||
/**
|
||||
* Resets the Mitsubishi protocol decoder state.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_mitsubishi_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feeds a pulse/gap into the Mitsubishi protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param level Signal level (true = high, false = low)
|
||||
* @param duration Duration of the level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Returns a hash of the decoded Mitsubishi data.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serializes the decoded Mitsubishi data into a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @param preset Pointer to the radio preset
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserializes Mitsubishi data from a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Formats the decoded Mitsubishi data into a human-readable string.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param output Pointer to the FuriString output buffer
|
||||
*/
|
||||
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output);
|
||||
291
lib/subghz/protocols/peugeot.c
Normal file
@@ -0,0 +1,291 @@
|
||||
#include "peugeot.h"
|
||||
|
||||
#define TAG "SubGhzProtocolPeugeot"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_peugeot_const = {
|
||||
.te_short = 370, // Short pulse duration
|
||||
.te_long = 772, // Long pulse duration (~2x short)
|
||||
.te_delta = 152, // Tolerance
|
||||
.min_count_bit_for_found = 66,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPeugeot {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
uint8_t packet_count;
|
||||
} SubGhzProtocolDecoderPeugeot;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderPeugeot {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} SubGhzProtocolEncoderPeugeot;
|
||||
|
||||
typedef enum {
|
||||
PeugeotDecoderStepReset = 0,
|
||||
PeugeotDecoderStepCheckPreamble,
|
||||
PeugeotDecoderStepSaveDuration,
|
||||
PeugeotDecoderStepCheckDuration,
|
||||
} PeugeotDecoderStep;
|
||||
|
||||
static void subghz_protocol_decoder_peugeot_reset_internal(SubGhzProtocolDecoderPeugeot* instance) {
|
||||
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->packet_count = 0;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_peugeot_decoder = {
|
||||
.alloc = subghz_protocol_decoder_peugeot_alloc,
|
||||
.free = subghz_protocol_decoder_peugeot_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_peugeot_feed,
|
||||
.reset = subghz_protocol_decoder_peugeot_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_peugeot_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_peugeot_serialize,
|
||||
.deserialize = subghz_protocol_decoder_peugeot_deserialize,
|
||||
.get_string = subghz_protocol_decoder_peugeot_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_peugeot_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_peugeot = {
|
||||
.name = PEUGEOT_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &subghz_protocol_peugeot_decoder,
|
||||
.encoder = &subghz_protocol_peugeot_encoder,
|
||||
};
|
||||
|
||||
// ----------------- Allocation / Reset / Free -------------------
|
||||
|
||||
void* subghz_protocol_decoder_peugeot_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderPeugeot* instance = calloc(1, sizeof(SubGhzProtocolDecoderPeugeot));
|
||||
instance->base.protocol = &subghz_protocol_peugeot;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_peugeot_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_peugeot_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_peugeot_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
subghz_protocol_decoder_peugeot_reset_internal(instance);
|
||||
}
|
||||
|
||||
// ----------------- Helper Functions -------------------
|
||||
|
||||
// Reverse 8 bits (LSB to MSB)
|
||||
static uint8_t reverse8(uint8_t byte) {
|
||||
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||
return byte;
|
||||
}
|
||||
|
||||
// Parse Keeloq data structure
|
||||
static bool subghz_protocol_peugeot_parse_data(SubGhzProtocolDecoderPeugeot* instance) {
|
||||
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||
|
||||
// Check preamble (first 12 bits should be 0xFFF)
|
||||
if(b[0] != 0xFF || (b[1] & 0xF0) != 0xF0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract encrypted part (32 bits) - reversed
|
||||
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) |
|
||||
(reverse8(b[2]) << 16) |
|
||||
(reverse8(b[1] & 0x0F) << 8) |
|
||||
reverse8(b[0]);
|
||||
|
||||
// Extract serial number (28 bits) - reversed
|
||||
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 20) |
|
||||
(reverse8(b[6]) << 12) |
|
||||
(reverse8(b[5]) << 4) |
|
||||
(reverse8(b[4]) >> 4);
|
||||
|
||||
// Extract button bits (4 bits from encrypted part)
|
||||
// Note: Button bits are (MSB/first sent to LSB) S3, S0, S1, S2
|
||||
uint8_t button_bits = (encrypted >> 28) & 0x0F;
|
||||
|
||||
// Store parsed data
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = button_bits;
|
||||
instance->generic.cnt = (encrypted >> 16) & 0xFFFF; // Counter from encrypted part
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------- Decoder Feed -------------------
|
||||
|
||||
void subghz_protocol_decoder_peugeot_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case PeugeotDecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta)) {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case PeugeotDecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
// High level - save duration
|
||||
if((DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
// Low level - check for warm-up pulses
|
||||
if((DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta)) {
|
||||
// Short pulse pair - part of warm-up
|
||||
instance->header_count++;
|
||||
} else if((DURATION_DIFF(duration, 4400) < 500) && instance->header_count >= 10) {
|
||||
// Long gap after warm-up pulses (~4400µs)
|
||||
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0ULL;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PeugeotDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
// High level - save duration
|
||||
if(duration >= (subghz_protocol_peugeot_const.te_long * 3)) {
|
||||
// Very long pulse - end of packet
|
||||
if(instance->decoder.decode_count_bit >=
|
||||
subghz_protocol_peugeot_const.min_count_bit_for_found) {
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
// Parse the Keeloq structure
|
||||
if(subghz_protocol_peugeot_parse_data(instance)) {
|
||||
instance->packet_count++;
|
||||
|
||||
// Call callback after receiving at least one packet
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
subghz_protocol_decoder_peugeot_reset_internal(instance);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = PeugeotDecoderStepCheckDuration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case PeugeotDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// PWM decoding: short-long = 0, long-short = 1
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_long) <
|
||||
subghz_protocol_peugeot_const.te_delta)) {
|
||||
// Short high, long low = 0
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_long) <
|
||||
subghz_protocol_peugeot_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||
subghz_protocol_peugeot_const.te_delta)) {
|
||||
// Long high, short low = 1
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------- API -------------------
|
||||
|
||||
uint8_t subghz_protocol_decoder_peugeot_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_peugeot_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_peugeot_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||
|
||||
uint32_t hi = instance->generic.data >> 32;
|
||||
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
|
||||
"Type:Keeloq/HCS\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
hi,
|
||||
lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt);
|
||||
}
|
||||
77
lib/subghz/protocols/peugeot.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define PEUGEOT_PROTOCOL_NAME "Peugeot"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_peugeot;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_peugeot_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_peugeot_encoder;
|
||||
|
||||
/**
|
||||
* Allocates memory for the Peugeot protocol decoder.
|
||||
* @param environment Pointer to SubGhzEnvironment
|
||||
* @return Pointer to the allocated decoder instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_peugeot_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Frees memory used by the Peugeot protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_peugeot_free(void* context);
|
||||
|
||||
/**
|
||||
* Resets the Peugeot protocol decoder state.
|
||||
* @param context Pointer to the decoder instance
|
||||
*/
|
||||
void subghz_protocol_decoder_peugeot_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feeds a pulse/gap into the Peugeot protocol decoder.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param level Signal level (true = high, false = low)
|
||||
* @param duration Duration of the level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_peugeot_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Returns a hash of the decoded Peugeot data.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_peugeot_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serializes the decoded Peugeot data into a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @param preset Pointer to the radio preset
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserializes Peugeot data from a FlipperFormat file.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param flipper_format Pointer to the FlipperFormat instance
|
||||
* @return SubGhzProtocolStatus result
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Formats the decoded Peugeot data into a human-readable string.
|
||||
* @param context Pointer to the decoder instance
|
||||
* @param output Pointer to the FuriString output buffer
|
||||
*/
|
||||
void subghz_protocol_decoder_peugeot_get_string(void* context, FuriString* output);
|
||||
@@ -43,6 +43,8 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
|
||||
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
|
||||
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_v0,
|
||||
&subghz_protocol_bmw, &subghz_protocol_mitsubishi_v1, &subghz_protocol_honda,
|
||||
&subghz_protocol_citroen, &subghz_protocol_peugeot,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "fiat_v0.h"
|
||||
#include "fiat_marelli.h"
|
||||
#include "subaru.h"
|
||||
#include "bmw.h"
|
||||
#include "kia_generic.h"
|
||||
#include "kia_v0.h"
|
||||
#include "kia_v1.h"
|
||||
@@ -72,5 +73,9 @@
|
||||
#include "kia_v6.h"
|
||||
#include "suzuki.h"
|
||||
#include "mitsubishi_v0.h"
|
||||
#include "mitsubishi_v1.h"
|
||||
#include "honda.h"
|
||||
#include "citroen.h"
|
||||
#include "peugeot.h"
|
||||
#include "mazda_siemens.h"
|
||||
#include "keys.h"
|
||||
|
||||