Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 048fcc39e4 | |||
| f5c211041b | |||
| 589a2e36f2 | |||
| 161e26f2dc | |||
| bf9ca01621 | |||
| 86f5aae002 | |||
| 46f3a5c993 | |||
| 52015fb289 | |||
| 23ba62cd69 | |||
| cd1e9d6945 | |||
| c49b843096 | |||
| 0c35337bb7 | |||
| e419b9865a |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
---
|
||||
|
||||
### Added
|
||||
- Protocol name allowlist filter: in Receiver Config, a new "Proto Filter"
|
||||
field accepts a comma-separated list of protocol names (e.g. "Ford V2,VAG").
|
||||
When set, the receiver ignores all decoded signals that are not in the list,
|
||||
reducing RAM usage and increasing the chance of capturing the target protocol.
|
||||
Leave empty to disable (default behavior, all protocols accepted).
|
||||
Setting is persisted in last_subghz.settings under the ProtocolFilter key.
|
||||
|
||||
### Changed
|
||||
- Protocol Filter: replaced free-text input with a dedicated protocol list
|
||||
scene (Proto Filter in Receiver Config). All registered protocols are shown
|
||||
as toggleable items (--- / ONLY). Selecting one or more protocols restricts
|
||||
the receiver to only show those; leaving all as --- disables the filter.
|
||||
The active count is shown inline in Receiver Config ("N set" or "All").
|
||||
Filter is persisted across sessions and cleared by Reset to default.
|
||||
@@ -36,6 +36,8 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Keeloq Key Manager | Mod Hopping Config |
|
||||
|  |  |
|
||||
| PSA XTEA Decrypt | Counter BruteForce |
|
||||
|  |  |
|
||||
| Custom Emulation Settings | Custom Emulation Scene |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -64,6 +64,10 @@ typedef enum {
|
||||
SubGhzCustomEventViewFreqAnalOkLong,
|
||||
|
||||
SubGhzCustomEventByteInputDone,
|
||||
SubGhzCustomEventCarEmulateTransmit,
|
||||
SubGhzCustomEventCarEmulateStop,
|
||||
SubGhzCustomEventCarEmulateExit,
|
||||
|
||||
} SubGhzCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
|
||||
@@ -94,6 +94,7 @@ typedef enum {
|
||||
SubGhzViewIdReadRAW,
|
||||
SubGhzViewIdPsaDecrypt,
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
SubGhzViewIdCarEmulate,
|
||||
|
||||
} SubGhzViewId;
|
||||
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
/**
|
||||
* Scene: CarEmulate
|
||||
* Custom automotive-key emulation GUI ported from ProtoPirate.
|
||||
* Activated when SubGhzLastSettings::custom_car_emulate == true and the
|
||||
* user presses "Emulate" on a saved dynamic protocol.
|
||||
*
|
||||
* Flow:
|
||||
* SavedMenu → Emulate → (custom_car_emulate?) CarEmulate : Transmitter
|
||||
*/
|
||||
#include "../subghz_i.h"
|
||||
#include "../views/subghz_car_emulate.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include "../helpers/subghz_txrx_i.h"
|
||||
#include <lib/subghz/blocks/custom_btn_i.h>
|
||||
|
||||
#define TAG "SubGhzSceneCarEmulate"
|
||||
#define MIN_TX_TICKS 66U /* ~666 ms at 100 ms tick */
|
||||
|
||||
/* ── Per-session state (heap, freed on exit) ─────────────────────────────── */
|
||||
typedef struct {
|
||||
/* Signal metadata read from fff_data */
|
||||
char protocol_name[48];
|
||||
uint32_t serial;
|
||||
uint8_t original_button;
|
||||
uint32_t original_counter;
|
||||
uint32_t current_counter;
|
||||
uint32_t freq;
|
||||
char preset_short[12]; /* "AM650", "FM476", … */
|
||||
|
||||
/* TX state */
|
||||
bool is_transmitting;
|
||||
bool stop_pending; /* stop requested before MIN_TX_TICKS elapsed */
|
||||
uint32_t tx_start_tick;
|
||||
|
||||
/* Pending button key (InputKey) decoded from the packed custom event */
|
||||
uint8_t pending_button;
|
||||
} CarEmulateState;
|
||||
|
||||
static CarEmulateState* s_state = NULL;
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Button mapping (protocol-name → InputKey → button byte)
|
||||
* Ported verbatim from protopirate_scene_emulate.c
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
//static uint8_t car_emulate_map_button(
|
||||
// const char* protocol,
|
||||
// InputKey key,
|
||||
// uint8_t original) {
|
||||
|
||||
/* Land Rover V0 */
|
||||
// if(strstr(protocol, "Land Rover")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x02; /* Lock */
|
||||
// case InputKeyOk: return 0x04; /* Unlock */
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Mazda */
|
||||
// if(strstr(protocol, "Mazda")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x01;
|
||||
// case InputKeyOk: return 0x02;
|
||||
// case InputKeyDown: return 0x04;
|
||||
// case InputKeyRight: return 0x08;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* PSA */
|
||||
// if(strstr(protocol, "PSA")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* VAG */
|
||||
// if(strstr(protocol, "VAG")) {
|
||||
// if(original == 0x10 || original == 0x20 || original == 0x40) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x20;
|
||||
// case InputKeyOk: return 0x10;
|
||||
// case InputKeyDown: return 0x40;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x2;
|
||||
// case InputKeyOk: return 0x1;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// case InputKeyRight: return 0x3;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Honda Static */
|
||||
// if(strstr(protocol, "Honda Static")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x4;
|
||||
// case InputKeyRight: return 0x5;
|
||||
// case InputKeyLeft: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Ford */
|
||||
// if(strstr(protocol, "Ford")) {
|
||||
// switch(key) {
|
||||
// case InputKeyLeft: return 0x1;
|
||||
// case InputKeyUp: return 0x2;
|
||||
// case InputKeyOk: return 0x4;
|
||||
// case InputKeyDown: return 0x8;
|
||||
// case InputKeyRight: return 0x10;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Chrysler */
|
||||
// if(strstr(protocol, "Chrysler")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Subaru */
|
||||
// if(strstr(protocol, "Subaru")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x3;
|
||||
// case InputKeyLeft: return 0x4;
|
||||
// case InputKeyRight: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Fiat V1 */
|
||||
// if(strstr(protocol, "Fiat V1")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x8;
|
||||
// case InputKeyOk: return 0x0;
|
||||
// case InputKeyDown: return 0xD;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
/* Generic KeeLoq / KIA etc. – simple 4-button layout */
|
||||
// if(strstr(protocol, "Kia") || strstr(protocol, "KIA") ||
|
||||
// strstr(protocol, "KeeLoq") || strstr(protocol, "Keeloq")) {
|
||||
// switch(key) {
|
||||
// case InputKeyUp: return 0x1;
|
||||
// case InputKeyOk: return 0x2;
|
||||
// case InputKeyDown: return 0x3;
|
||||
// case InputKeyLeft: return 0x4;
|
||||
// case InputKeyRight: return 0x8;
|
||||
// default: return original;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return original;
|
||||
//}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* TX helpers
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
|
||||
/**
|
||||
* Read frequency and short preset name from fff_data.
|
||||
* Falls back to 433.92 MHz / "AM650" on failure.
|
||||
*/
|
||||
static void car_emulate_read_freq_preset(SubGhz* subghz, CarEmulateState* st) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
|
||||
st->freq = 433920000UL;
|
||||
strncpy(st->preset_short, "AM650", sizeof(st->preset_short) - 1);
|
||||
|
||||
if(!fff) return;
|
||||
|
||||
uint32_t freq = 0;
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_uint32(fff, "Frequency", &freq, 1) && freq > 0) {
|
||||
st->freq = freq;
|
||||
}
|
||||
|
||||
FuriString* preset_str = furi_string_alloc();
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Preset", preset_str)) {
|
||||
/* Convert long FuriHal name → short token used by the setting */
|
||||
const char* raw = furi_string_get_cstr(preset_str);
|
||||
const char* short_name = "AM650";
|
||||
if(strstr(raw, "Ook270")) short_name = "AM270";
|
||||
else if(strstr(raw, "Ook650")) short_name = "AM650";
|
||||
else if(strstr(raw, "238")) short_name = "FM238";
|
||||
else if(strstr(raw, "12K")) short_name = "FM12K";
|
||||
else if(strstr(raw, "476")) short_name = "FM476";
|
||||
else if(strstr(raw, "Custom")) short_name = "CUST";
|
||||
strncpy(st->preset_short, short_name, sizeof(st->preset_short) - 1);
|
||||
}
|
||||
furi_string_free(preset_str);
|
||||
}
|
||||
|
||||
/** Update Btn and Cnt fields in fff_data so the transmitter re-serialises them. */
|
||||
static void car_emulate_apply_button(SubGhz* subghz, InputKey key) {
|
||||
UNUSED(subghz);
|
||||
|
||||
uint8_t custom_btn_id;
|
||||
switch(key) {
|
||||
case InputKeyUp: custom_btn_id = SUBGHZ_CUSTOM_BTN_UP; break;
|
||||
case InputKeyDown: custom_btn_id = SUBGHZ_CUSTOM_BTN_DOWN; break;
|
||||
case InputKeyLeft: custom_btn_id = SUBGHZ_CUSTOM_BTN_LEFT; break;
|
||||
case InputKeyRight: custom_btn_id = SUBGHZ_CUSTOM_BTN_RIGHT; break;
|
||||
case InputKeyOk:
|
||||
default: custom_btn_id = SUBGHZ_CUSTOM_BTN_OK; break;
|
||||
}
|
||||
|
||||
subghz_custom_btn_set(custom_btn_id);
|
||||
}
|
||||
|
||||
|
||||
/** Update Cnt in fff_data (Btn is handled by the protocol via custom_btn). */
|
||||
static void car_emulate_update_fff(SubGhz* subghz, uint32_t counter) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
if(!fff) return;
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_insert_or_update_uint32(fff, "Cnt", &counter, 1);
|
||||
}
|
||||
|
||||
|
||||
/** Apply tx_power to the current preset and start a single transmission burst. */
|
||||
static bool car_emulate_start_tx(SubGhz* subghz, uint8_t custom_btn_id) {
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
if(preset.data && preset.data_size > 0 && subghz->tx_power > 0) {
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, subghz->tx_power);
|
||||
FURI_LOG_I(TAG, "TX power index applied: %u", subghz->tx_power);
|
||||
}
|
||||
|
||||
subghz_custom_btn_set(custom_btn_id);
|
||||
|
||||
bool ok = subghz_tx_start(subghz, subghz_txrx_get_fff_data(subghz->txrx));
|
||||
if(ok) {
|
||||
subghz->state_notifications = SubGhzNotificationStateTx;
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
FURI_LOG_I(TAG, "TX started");
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "subghz_tx_start failed");
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
/** Stop an active transmission. */
|
||||
static void car_emulate_stop_tx(SubGhz* subghz) {
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
FURI_LOG_I(TAG, "TX stopped");
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* View callback (fired from the View's input handler)
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
static void subghz_scene_car_emulate_view_callback(uint32_t event, void* context) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Helpers to keep the view in sync
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
static void car_emulate_refresh_view(SubGhz* subghz) {
|
||||
furi_assert(s_state);
|
||||
subghz_car_emulate_view_set_data(
|
||||
subghz->car_emulate_view,
|
||||
s_state->protocol_name,
|
||||
s_state->serial,
|
||||
s_state->current_counter,
|
||||
s_state->original_counter,
|
||||
s_state->freq,
|
||||
s_state->preset_short,
|
||||
s_state->is_transmitting);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_enter
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
void subghz_scene_car_emulate_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(subghz);
|
||||
|
||||
/* Allocate per-session state */
|
||||
s_state = malloc(sizeof(CarEmulateState));
|
||||
furi_check(s_state);
|
||||
memset(s_state, 0, sizeof(CarEmulateState));
|
||||
|
||||
/* ── Read metadata from the loaded fff_data ── */
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
if(fff) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
if(flipper_format_read_string(fff, "Protocol", tmp)) {
|
||||
strncpy(
|
||||
s_state->protocol_name,
|
||||
furi_string_get_cstr(tmp),
|
||||
sizeof(s_state->protocol_name) - 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Serial", &s_state->serial, 1);
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
uint32_t btn_tmp = 0;
|
||||
if(flipper_format_read_uint32(fff, "Btn", &btn_tmp, 1)) {
|
||||
s_state->original_button = (uint8_t)btn_tmp;
|
||||
}
|
||||
|
||||
flipper_format_rewind(fff);
|
||||
flipper_format_read_uint32(fff, "Cnt", &s_state->original_counter, 1);
|
||||
s_state->current_counter = s_state->original_counter;
|
||||
|
||||
furi_string_free(tmp);
|
||||
}
|
||||
|
||||
/* ── Initialize the custom_btn system ──────────────────────────────────
|
||||
* Reset first so any leftover state from a previous session is cleared.
|
||||
* Then deserialize the decoder once: this causes the protocol's own
|
||||
* deserialize() to call subghz_custom_btn_set_original() and
|
||||
* subghz_custom_btn_set_max(), which is exactly what the standard
|
||||
* Transmitter scene does via subghz_scene_transmitter_update_data_show().
|
||||
* After this call:
|
||||
* - subghz_custom_btn_get_original() → the button that was in the file
|
||||
* - subghz_custom_btn_is_allowed() → true if protocol supports it
|
||||
* - subghz_custom_btn_get_max() → number of buttons available */
|
||||
subghz_custom_btns_reset();
|
||||
|
||||
SubGhzProtocolDecoderBase* decoder = subghz_txrx_get_decoder(subghz->txrx);
|
||||
if(decoder && fff) {
|
||||
flipper_format_rewind(fff);
|
||||
subghz_protocol_decoder_base_deserialize(decoder, fff);
|
||||
/* Rewind again so subsequent reads in car_emulate_read_freq_preset()
|
||||
* start from the beginning of the file. */
|
||||
flipper_format_rewind(fff);
|
||||
}
|
||||
|
||||
subghz_car_emulate_view_set_labels(
|
||||
subghz->car_emulate_view,
|
||||
"UNLOCK", /* OK */
|
||||
"LOCK", /* Up */
|
||||
"TRUNK", /* Down */
|
||||
"PANIC", /* Left */
|
||||
"START" /* Right */
|
||||
);
|
||||
|
||||
car_emulate_read_freq_preset(subghz, s_state);
|
||||
|
||||
/* ── Configure the view ── */
|
||||
subghz_car_emulate_view_set_callback(
|
||||
subghz->car_emulate_view, subghz_scene_car_emulate_view_callback, subghz);
|
||||
|
||||
car_emulate_refresh_view(subghz);
|
||||
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_event
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
bool subghz_scene_car_emulate_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(s_state);
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
|
||||
/* ── Transmit ── */
|
||||
if((event.event & 0xFFFFU) == SubGhzCustomEventCarEmulateTransmit) {
|
||||
InputKey key = (InputKey)((event.event >> 16) & 0xFFU);
|
||||
|
||||
/* Stop any ongoing TX first */
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
|
||||
/* Bump counter */
|
||||
s_state->current_counter++;
|
||||
|
||||
/* Set the custom button BEFORE deserialize() is called inside
|
||||
* subghz_tx_start() → subghz_txrx_tx_start().
|
||||
* The protocol's deserialize() will call subghz_custom_btn_get()
|
||||
* to pick the right button code. */
|
||||
car_emulate_apply_button(subghz, key);
|
||||
|
||||
/* Only update the counter in fff_data; the protocol handles Btn. */
|
||||
car_emulate_update_fff(subghz, s_state->current_counter);
|
||||
|
||||
s_state->is_transmitting = true;
|
||||
s_state->stop_pending = false;
|
||||
s_state->tx_start_tick = (uint32_t)furi_get_tick();
|
||||
|
||||
uint8_t cur_btn = subghz_custom_btn_get();
|
||||
if(!car_emulate_start_tx(subghz, cur_btn)) {
|
||||
s_state->is_transmitting = false;
|
||||
notification_message(subghz->notifications, &sequence_error);
|
||||
}
|
||||
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
|
||||
/* ── Stop ── */
|
||||
} else if(event.event == SubGhzCustomEventCarEmulateStop) {
|
||||
if(s_state->is_transmitting &&
|
||||
subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
|
||||
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
|
||||
if(elapsed >= MIN_TX_TICKS) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
} else {
|
||||
s_state->stop_pending = true;
|
||||
}
|
||||
}
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
|
||||
/* ── Exit ── */
|
||||
} else if(event.event == SubGhzCustomEventCarEmulateExit) {
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
scene_manager_search_and_switch_to_previous_scene(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
} else if(event.type == SceneManagerEventTypeTick) {
|
||||
|
||||
if(s_state->is_transmitting &&
|
||||
subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
|
||||
/* Check if hardware is done */
|
||||
if(subghz_devices_is_async_complete_tx(subghz->txrx->radio_device)) {
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
subghz_txrx_stop(subghz->txrx);
|
||||
|
||||
if(s_state->stop_pending) {
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
}
|
||||
} else {
|
||||
/* Still transmitting – blink LED */
|
||||
notification_message(subghz->notifications, &sequence_blink_magenta_10);
|
||||
}
|
||||
|
||||
/* Enforce MIN_TX_TICKS stop gate */
|
||||
if(s_state->stop_pending) {
|
||||
uint32_t elapsed = (uint32_t)furi_get_tick() - s_state->tx_start_tick;
|
||||
if(elapsed >= MIN_TX_TICKS) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
s_state->is_transmitting = false;
|
||||
s_state->stop_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Refresh view every tick for animation */
|
||||
car_emulate_refresh_view(subghz);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* Scene on_exit
|
||||
* ═════════════════════════════════════════════════════════════════════════*/
|
||||
void subghz_scene_car_emulate_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(subghz->state_notifications == SubGhzNotificationStateTx) {
|
||||
car_emulate_stop_tx(subghz);
|
||||
}
|
||||
|
||||
subghz->state_notifications = SubGhzNotificationStateIDLE;
|
||||
notification_message(subghz->notifications, &sequence_blink_stop);
|
||||
|
||||
/* Clear view callbacks */
|
||||
subghz_car_emulate_view_set_callback(subghz->car_emulate_view, NULL, NULL);
|
||||
|
||||
/* Free per-session state */
|
||||
if(s_state) {
|
||||
free(s_state);
|
||||
s_state = NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Scene: CarEmulateSettings
|
||||
* Toggle: Custom Emulate Off / On
|
||||
* Selector: TX Power (reuses the same table as Radio Settings)
|
||||
* Both settings are persisted in SubGhzLastSettings.
|
||||
*/
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/toolbox/value_index.h>
|
||||
|
||||
#define TAG "SubGhzCarEmulateSettings"
|
||||
|
||||
/* ── Toggle ──────────────────────────────────────────────────────────────── */
|
||||
static const char* const toggle_text[] = {"Off", "On"};
|
||||
|
||||
static void subghz_scene_car_emulate_settings_toggle_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
furi_assert(subghz);
|
||||
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, toggle_text[index]);
|
||||
|
||||
subghz->last_settings->custom_car_emulate = (index == 1);
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
/* ── TX Power ────────────────────────────────────────────────────────────── */
|
||||
/* Must match the table in subghz_scene_radio_settings.c exactly */
|
||||
#define CE_TX_POWER_COUNT 9
|
||||
static const char* const ce_tx_power_text[CE_TX_POWER_COUNT] = {
|
||||
"Preset", /* index 0 → use whatever the preset has baked in */
|
||||
"10dBm +",
|
||||
"7dBm",
|
||||
"5dBm",
|
||||
"0dBm",
|
||||
"-10dBm",
|
||||
"-15dBm",
|
||||
"-20dBm",
|
||||
"-30dBm",
|
||||
};
|
||||
|
||||
static void subghz_scene_car_emulate_settings_power_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
furi_assert(subghz);
|
||||
|
||||
uint8_t index = variable_item_get_current_value_index(item);
|
||||
variable_item_set_current_value_text(item, ce_tx_power_text[index]);
|
||||
|
||||
/* Mirror the same fields that Radio Settings touches so the value is
|
||||
* visible everywhere and survives app restart. */
|
||||
subghz->tx_power = index;
|
||||
subghz->last_settings->tx_power = index;
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
|
||||
/* Patch the live preset buffer immediately so any subsequent TX in this
|
||||
* session uses the new power without needing a restart. */
|
||||
SubGhzRadioPreset preset = subghz_txrx_get_preset(subghz->txrx);
|
||||
if(preset.data && preset.data_size > 0) {
|
||||
subghz_txrx_set_tx_power(preset.data, preset.data_size, index);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Scene callbacks ─────────────────────────────────────────────────────── */
|
||||
void subghz_scene_car_emulate_settings_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
furi_assert(subghz);
|
||||
|
||||
VariableItemList* list = subghz->variable_item_list;
|
||||
variable_item_list_reset(list);
|
||||
|
||||
/* ── Row 1: Custom Emulate toggle ── */
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
"Custom Emulate",
|
||||
2,
|
||||
subghz_scene_car_emulate_settings_toggle_changed,
|
||||
subghz);
|
||||
|
||||
uint8_t toggle_idx = subghz->last_settings->custom_car_emulate ? 1 : 0;
|
||||
variable_item_set_current_value_index(item, toggle_idx);
|
||||
variable_item_set_current_value_text(item, toggle_text[toggle_idx]);
|
||||
|
||||
/* ── Row 2: TX Power ── */
|
||||
item = variable_item_list_add(
|
||||
list,
|
||||
"TX Power",
|
||||
CE_TX_POWER_COUNT,
|
||||
subghz_scene_car_emulate_settings_power_changed,
|
||||
subghz);
|
||||
|
||||
/* Clamp stored value to valid range in case settings file is corrupt */
|
||||
uint8_t power_idx = subghz->tx_power;
|
||||
if(power_idx >= CE_TX_POWER_COUNT) power_idx = 0;
|
||||
|
||||
variable_item_set_current_value_index(item, power_idx);
|
||||
variable_item_set_current_value_text(item, ce_tx_power_text[power_idx]);
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_car_emulate_settings_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void subghz_scene_car_emulate_settings_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
}
|
||||
@@ -34,3 +34,5 @@ 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)
|
||||
ADD_SCENE(subghz, car_emulate, CarEmulate)
|
||||
ADD_SCENE(subghz, car_emulate_settings, CarEmulateSettings)
|
||||
|
||||
@@ -1,53 +1,135 @@
|
||||
#include "../subghz_i.h"
|
||||
#include <lib/subghz/subghz_protocol_registry.h>
|
||||
|
||||
void subghz_scene_protocol_list_submenu_callback(void* context, uint32_t index) {
|
||||
SubGhz* subghz = context;
|
||||
view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
|
||||
}
|
||||
#define TAG "SubGhzSceneProtocolList"
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
/* ── helpers ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
submenu_reset(subghz->submenu);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
char header_str[32];
|
||||
snprintf(header_str, sizeof(header_str), "Protocols: %zu", protocol_count);
|
||||
submenu_set_header(subghz->submenu, header_str);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(protocol) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
protocol->name,
|
||||
i,
|
||||
subghz_scene_protocol_list_submenu_callback,
|
||||
subghz);
|
||||
}
|
||||
}
|
||||
|
||||
submenu_set_selected_item(
|
||||
subghz->submenu,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
return true;
|
||||
static bool proto_filter_contains(const char* filter, const char* name) {
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(name) && strncmp(p, name, len) == 0) return true;
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void proto_filter_toggle(char* filter, size_t filter_size, const char* name) {
|
||||
if(proto_filter_contains(filter, name)) {
|
||||
/* remove it */
|
||||
char tmp[256] = {0};
|
||||
const char* p = filter;
|
||||
bool first = true;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(!(len == strlen(name) && strncmp(p, name, len) == 0)) {
|
||||
if(!first) strncat(tmp, ",", sizeof(tmp) - strlen(tmp) - 1);
|
||||
strncat(tmp, p, len < sizeof(tmp) - strlen(tmp) - 1 ? len : 0);
|
||||
first = false;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
strncpy(filter, tmp, filter_size - 1);
|
||||
filter[filter_size - 1] = '\0';
|
||||
} else {
|
||||
/* add it */
|
||||
if(filter[0] != '\0') strncat(filter, ",", filter_size - strlen(filter) - 1);
|
||||
strncat(filter, name, filter_size - strlen(filter) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── callbacks ────────────────────────────────────────────────────────────── */
|
||||
|
||||
static void subghz_scene_protocol_list_item_changed(VariableItem* item) {
|
||||
SubGhz* subghz = variable_item_get_context(item);
|
||||
uint8_t value_index = variable_item_get_current_value_index(item);
|
||||
|
||||
uint32_t proto_idx =
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
size_t selected =
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list);
|
||||
UNUSED(proto_idx);
|
||||
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, selected);
|
||||
if(!protocol) return;
|
||||
|
||||
const char* name = protocol->name;
|
||||
|
||||
bool currently_in =
|
||||
proto_filter_contains(subghz->last_settings->protocol_filter, name);
|
||||
|
||||
if((value_index == 1) != currently_in) {
|
||||
proto_filter_toggle(
|
||||
subghz->last_settings->protocol_filter,
|
||||
sizeof(subghz->last_settings->protocol_filter),
|
||||
name);
|
||||
}
|
||||
|
||||
variable_item_set_current_value_text(item, value_index ? "ONLY" : "---");
|
||||
subghz_last_settings_save(subghz->last_settings);
|
||||
}
|
||||
|
||||
/* ── scene callbacks ──────────────────────────────────────────────────────── */
|
||||
|
||||
void subghz_scene_protocol_list_on_enter(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
VariableItemList* list = subghz->variable_item_list;
|
||||
variable_item_list_reset(list);
|
||||
|
||||
size_t protocol_count = subghz_protocol_registry_count(&subghz_protocol_registry);
|
||||
|
||||
for(size_t i = 0; i < protocol_count; i++) {
|
||||
const SubGhzProtocol* protocol =
|
||||
subghz_protocol_registry_get_by_index(&subghz_protocol_registry, i);
|
||||
if(!protocol) continue;
|
||||
|
||||
VariableItem* item = variable_item_list_add(
|
||||
list,
|
||||
protocol->name,
|
||||
2,
|
||||
subghz_scene_protocol_list_item_changed,
|
||||
subghz);
|
||||
|
||||
bool enabled = proto_filter_contains(
|
||||
subghz->last_settings->protocol_filter, protocol->name);
|
||||
variable_item_set_current_value_index(item, enabled ? 1 : 0);
|
||||
variable_item_set_current_value_text(item, enabled ? "ONLY" : "---");
|
||||
}
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
list,
|
||||
scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneProtocolList));
|
||||
|
||||
view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdVariableItemList);
|
||||
}
|
||||
|
||||
bool subghz_scene_protocol_list_on_event(void* context, SceneManagerEvent event) {
|
||||
SubGhz* subghz = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneProtocolList, event.event);
|
||||
consumed = true;
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
scene_manager_previous_scene(subghz->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void subghz_scene_protocol_list_on_exit(void* context) {
|
||||
SubGhz* subghz = context;
|
||||
submenu_reset(subghz->submenu);
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneProtocolList,
|
||||
variable_item_list_get_selected_item_index(subghz->variable_item_list));
|
||||
variable_item_list_reset(subghz->variable_item_list);
|
||||
}
|
||||
|
||||
@@ -105,6 +105,29 @@ static void subghz_scene_add_to_history_callback(
|
||||
SubGhz* subghz = context;
|
||||
|
||||
// The check can be moved to /lib/subghz/receiver.c, but may result in false positives
|
||||
/* Protocol name allowlist filter — if non-empty, drop anything not in the list */
|
||||
if(subghz->last_settings->protocol_filter[0] != '\0') {
|
||||
const char* proto_name = decoder_base->protocol->name;
|
||||
const char* filter = subghz->last_settings->protocol_filter;
|
||||
bool allowed = false;
|
||||
/* Walk the comma-separated list */
|
||||
const char* p = filter;
|
||||
while(*p) {
|
||||
const char* comma = strchr(p, ',');
|
||||
size_t len = comma ? (size_t)(comma - p) : strlen(p);
|
||||
if(len == strlen(proto_name) && strncmp(p, proto_name, len) == 0) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
if(!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
if(!allowed) {
|
||||
FURI_LOG_D(TAG, "%s filtered by allowlist", proto_name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if((decoder_base->protocol->flag & subghz->ignore_filter) == 0) {
|
||||
SubGhzHistory* history = subghz->history;
|
||||
FuriString* item_name = furi_string_alloc();
|
||||
|
||||
@@ -17,6 +17,7 @@ enum SubGhzSettingIndex {
|
||||
SubGhzSettingIndexIgnoreNiceFlorS,
|
||||
SubGhzSettingIndexDeleteOldSignals,
|
||||
SubGhzSettingIndexSound,
|
||||
SubGhzSettingIndexProtoFilter,
|
||||
SubGhzSettingIndexResetToDefault,
|
||||
SubGhzSettingIndexLock,
|
||||
SubGhzSettingIndexRAWThresholdRSSI,
|
||||
@@ -445,7 +446,9 @@ static void subghz_scene_receiver_config_set_delete_old_signals(VariableItem* it
|
||||
static void subghz_scene_receiver_config_var_list_enter_callback(void* context, uint32_t index) {
|
||||
furi_assert(context);
|
||||
SubGhz* subghz = context;
|
||||
if(index == SubGhzSettingIndexLock) {
|
||||
if(index == SubGhzSettingIndexProtoFilter) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneProtocolList);
|
||||
} else if(index == SubGhzSettingIndexLock) {
|
||||
view_dispatcher_send_custom_event(
|
||||
subghz->view_dispatcher, SubGhzCustomEventSceneSettingLock);
|
||||
} else if(index == SubGhzSettingIndexResetToDefault) {
|
||||
@@ -473,6 +476,7 @@ static void subghz_scene_receiver_config_var_list_enter_callback(void* context,
|
||||
subghz->last_settings->filter = subghz->filter;
|
||||
subghz->last_settings->delete_old_signals = false;
|
||||
subghz->last_settings->tx_power = subghz->tx_power = 0;
|
||||
subghz->last_settings->protocol_filter[0] = '\0';
|
||||
subghz_txrx_speaker_set_state(subghz->txrx, speaker_value[default_index]);
|
||||
|
||||
subghz_txrx_hopper_set_state(subghz->txrx, hopping_value[default_index]);
|
||||
@@ -668,6 +672,25 @@ void subghz_scene_receiver_config_on_enter(void* context) {
|
||||
|
||||
if(scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneReadRAW) !=
|
||||
SubGhzCustomEventManagerSet) {
|
||||
/* Protocol filter */
|
||||
item = variable_item_list_add(
|
||||
subghz->variable_item_list,
|
||||
"Proto Filter",
|
||||
1,
|
||||
NULL,
|
||||
subghz);
|
||||
if(subghz->last_settings->protocol_filter[0] == '\0') {
|
||||
variable_item_set_current_value_text(item, "All");
|
||||
} else {
|
||||
uint8_t count = 1;
|
||||
for(const char* p = subghz->last_settings->protocol_filter; *p; p++) {
|
||||
if(*p == ',') count++;
|
||||
}
|
||||
static char filter_count_str[8];
|
||||
snprintf(filter_count_str, sizeof(filter_count_str), "%u set", count);
|
||||
variable_item_set_current_value_text(item, filter_count_str);
|
||||
}
|
||||
|
||||
// Reset to default
|
||||
variable_item_list_add(subghz->variable_item_list, "Reset to default", 1, NULL, NULL);
|
||||
|
||||
|
||||
@@ -6,7 +6,8 @@ enum SubmenuIndex {
|
||||
SubmenuIndexEdit,
|
||||
SubmenuIndexDelete,
|
||||
SubmenuIndexSignalSettings,
|
||||
SubmenuIndexCounterBf
|
||||
SubmenuIndexCounterBf, /* <-- comma was missing here */
|
||||
SubmenuIndexCarEmulateSettings,
|
||||
};
|
||||
|
||||
void subghz_scene_saved_menu_submenu_callback(void* context, uint32_t index) {
|
||||
@@ -77,6 +78,13 @@ void subghz_scene_saved_menu_on_enter(void* context) {
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
"Custom Emulate Settings",
|
||||
SubmenuIndexCarEmulateSettings,
|
||||
subghz_scene_saved_menu_submenu_callback,
|
||||
subghz);
|
||||
|
||||
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
|
||||
submenu_add_item(
|
||||
subghz->submenu,
|
||||
@@ -109,7 +117,22 @@ bool subghz_scene_saved_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
if(event.event == SubmenuIndexEmulate) {
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager, SubGhzSceneSavedMenu, SubmenuIndexEmulate);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
|
||||
bool use_custom = subghz->last_settings->custom_car_emulate;
|
||||
if(use_custom) {
|
||||
FlipperFormat* fff = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
uint32_t cnt_tmp = 0;
|
||||
flipper_format_rewind(fff);
|
||||
if(!flipper_format_read_uint32(fff, "Cnt", &cnt_tmp, 1)) {
|
||||
use_custom = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(use_custom) {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
|
||||
} else {
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
}
|
||||
return true;
|
||||
} else if(event.event == SubmenuIndexPsaDecrypt) {
|
||||
scene_manager_set_scene_state(
|
||||
@@ -136,6 +159,14 @@ 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 == SubmenuIndexCarEmulateSettings) {
|
||||
/* <-- was outside the if block due to misplaced brace, now fixed */
|
||||
scene_manager_set_scene_state(
|
||||
subghz->scene_manager,
|
||||
SubGhzSceneSavedMenu,
|
||||
SubmenuIndexCarEmulateSettings);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulateSettings);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -206,6 +206,12 @@ SubGhz* subghz_alloc(bool alloc_for_tx_only) {
|
||||
SubGhzViewIdKeeloqDecrypt,
|
||||
subghz_view_keeloq_decrypt_get_view(subghz->subghz_keeloq_decrypt));
|
||||
|
||||
subghz->car_emulate_view = subghz_car_emulate_view_alloc();
|
||||
view_dispatcher_add_view(
|
||||
subghz->view_dispatcher,
|
||||
SubGhzViewIdCarEmulate,
|
||||
subghz_car_emulate_view_get_view(subghz->car_emulate_view));
|
||||
|
||||
//init threshold rssi
|
||||
subghz->threshold_rssi = subghz_threshold_rssi_alloc();
|
||||
|
||||
@@ -321,6 +327,10 @@ void subghz_free(SubGhz* subghz, bool alloc_for_tx_only) {
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdKeeloqDecrypt);
|
||||
subghz_view_keeloq_decrypt_free(subghz->subghz_keeloq_decrypt);
|
||||
|
||||
// Custom car-emulate view
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdCarEmulate);
|
||||
subghz_car_emulate_view_free(subghz->car_emulate_view);
|
||||
|
||||
// Read RAW
|
||||
view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReadRAW);
|
||||
subghz_read_raw_free(subghz->subghz_read_raw);
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#include "helpers/subghz_txrx.h"
|
||||
#include "helpers/subghz_keeloq_keys.h"
|
||||
|
||||
#include "views/subghz_car_emulate.h"
|
||||
|
||||
#define SUBGHZ_MAX_LEN_NAME 64
|
||||
#define SUBGHZ_EXT_PRESET_NAME true
|
||||
#define SUBGHZ_RAW_THRESHOLD_MIN (-90.0f)
|
||||
@@ -76,6 +78,7 @@ struct SubGhz {
|
||||
SubGhzReadRAW* subghz_read_raw;
|
||||
SubGhzViewPsaDecrypt* subghz_psa_decrypt;
|
||||
SubGhzViewKeeloqDecrypt* subghz_keeloq_decrypt;
|
||||
SubGhzCarEmulateView* car_emulate_view;
|
||||
bool raw_send_only;
|
||||
|
||||
bool save_datetime_set;
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_HOPPING_THRESHOLD "HoppingThreshold"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP "LedAndPowerAmp"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_TX_POWER "TXPower"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE "CustomCarEmulate"
|
||||
#define SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER "ProtocolFilter"
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void) {
|
||||
SubGhzLastSettings* instance = malloc(sizeof(SubGhzLastSettings));
|
||||
@@ -50,6 +52,7 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
instance->enable_preset_hopping = false;
|
||||
instance->preset_hopping_threshold = SUBGHZ_LAST_SETTING_DEFAULT_PRESET_HOPPING_THRESHOLD;
|
||||
instance->leds_and_amp = true;
|
||||
instance->protocol_filter[0] = '\0';
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
|
||||
@@ -163,6 +166,27 @@ void subghz_last_settings_load(SubGhzLastSettings* instance, size_t preset_count
|
||||
1)) {
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
if(!flipper_format_read_bool(
|
||||
fff_data_file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
|
||||
&instance->custom_car_emulate,
|
||||
1)) {
|
||||
instance->custom_car_emulate = false;
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
FuriString* filter_str = furi_string_alloc();
|
||||
if(flipper_format_read_string(
|
||||
fff_data_file, SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER, filter_str)) {
|
||||
strncpy(
|
||||
instance->protocol_filter,
|
||||
furi_string_get_cstr(filter_str),
|
||||
sizeof(instance->protocol_filter) - 1);
|
||||
instance->protocol_filter[sizeof(instance->protocol_filter) - 1] = '\0';
|
||||
} else {
|
||||
instance->protocol_filter[0] = '\0';
|
||||
flipper_format_rewind(fff_data_file);
|
||||
}
|
||||
furi_string_free(filter_str);
|
||||
|
||||
} while(0);
|
||||
} else {
|
||||
@@ -281,6 +305,19 @@ bool subghz_last_settings_save(SubGhzLastSettings* instance) {
|
||||
file, SUBGHZ_LAST_SETTING_FIELD_LED_AND_POWER_AMP, &instance->leds_and_amp, 1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_bool(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_CUSTOM_CAR_EMULATE,
|
||||
&instance->custom_car_emulate,
|
||||
1)) {
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_write_string_cstr(
|
||||
file,
|
||||
SUBGHZ_LAST_SETTING_FIELD_PROTOCOL_FILTER,
|
||||
instance->protocol_filter)) {
|
||||
break;
|
||||
}
|
||||
|
||||
saved = true;
|
||||
} while(0);
|
||||
|
||||
@@ -30,6 +30,8 @@ typedef struct {
|
||||
float preset_hopping_threshold;
|
||||
bool leds_and_amp;
|
||||
uint8_t tx_power;
|
||||
bool custom_car_emulate;
|
||||
char protocol_filter[256]; /* comma-separated allowlist, empty = disabled */
|
||||
} SubGhzLastSettings;
|
||||
|
||||
SubGhzLastSettings* subghz_last_settings_alloc(void);
|
||||
|
||||
@@ -0,0 +1,264 @@
|
||||
#include "subghz_car_emulate.h"
|
||||
#include "../helpers/subghz_custom_event.h"
|
||||
|
||||
#include <gui/elements.h>
|
||||
#include <input/input.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "SubGhzCarEmulateView"
|
||||
|
||||
/* ── Model ──────────────────────────────────────────────────────────────── */
|
||||
typedef struct {
|
||||
char protocol_name[32];
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint32_t original_counter;
|
||||
uint32_t freq;
|
||||
char preset[12];
|
||||
bool is_transmitting;
|
||||
uint8_t anim_frame;
|
||||
char label_ok[12];
|
||||
char label_up[12];
|
||||
char label_down[12];
|
||||
char label_left[12];
|
||||
char label_right[12];
|
||||
} SubGhzCarEmulateViewModel;
|
||||
|
||||
/* ── Handle ─────────────────────────────────────────────────────────────── */
|
||||
struct SubGhzCarEmulateView {
|
||||
View* view;
|
||||
SubGhzCarEmulateViewCallback callback;
|
||||
void* context;
|
||||
};
|
||||
|
||||
/* ── Draw ───────────────────────────────────────────────────────────────── */
|
||||
static void subghz_car_emulate_view_draw(Canvas* canvas, void* model_ptr) {
|
||||
SubGhzCarEmulateViewModel* m = model_ptr;
|
||||
|
||||
m->anim_frame = (m->anim_frame + 1) % 8;
|
||||
|
||||
canvas_clear(canvas);
|
||||
|
||||
/* Header bar */
|
||||
canvas_draw_box(canvas, 0, 0, 128, 11);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, m->protocol_name);
|
||||
canvas_invert_color(canvas);
|
||||
|
||||
/* Info row 1: serial + counter */
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buf[32];
|
||||
|
||||
if(m->serial <= 0xFFFFFFUL) {
|
||||
snprintf(buf, sizeof(buf), "SN:%06lX", (unsigned long)(m->serial & 0xFFFFFFUL));
|
||||
} else {
|
||||
snprintf(buf, sizeof(buf), "SN:%08lX", (unsigned long)m->serial);
|
||||
}
|
||||
canvas_draw_str(canvas, 2, 20, buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "CNT:%04lX", (unsigned long)m->counter);
|
||||
canvas_draw_str(canvas, 68, 20, buf);
|
||||
|
||||
if(m->counter > m->original_counter) {
|
||||
snprintf(buf, sizeof(buf), "+%ld", (long)(m->counter - m->original_counter));
|
||||
canvas_draw_str(canvas, 112, 20, buf);
|
||||
}
|
||||
|
||||
/* Info row 2: frequency + preset */
|
||||
snprintf(
|
||||
buf,
|
||||
sizeof(buf),
|
||||
"F:%lu.%02lu",
|
||||
(unsigned long)(m->freq / 1000000UL),
|
||||
(unsigned long)((m->freq % 1000000UL) / 10000UL));
|
||||
canvas_draw_str(canvas, 2, 30, buf);
|
||||
canvas_draw_str(canvas, 95, 30, m->preset);
|
||||
|
||||
/* ── Button labels ── */
|
||||
const uint8_t font_h = canvas_current_font_height(canvas);
|
||||
|
||||
/* Centre → UNLOCK (OK button) */
|
||||
{
|
||||
const char* lbl = m->label_ok;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 45 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 49, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Up → LOCK */
|
||||
{
|
||||
const char* lbl = m->label_up;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 33 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 37, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Left → PANIC */
|
||||
{
|
||||
const char* lbl = m->label_left;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 0, 46 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, w / 2, 50, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Right → generic extra */
|
||||
{
|
||||
const char* lbl = m->label_right;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 127 - w, 46 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 127 - w / 2, 50, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* Down → BOOT */
|
||||
{
|
||||
const char* lbl = m->label_down;
|
||||
uint8_t w = (uint8_t)(canvas_string_width(canvas, lbl) + 8U);
|
||||
canvas_draw_rbox(canvas, 64 - w / 2, 57 - font_h / 2, w, font_h, 3);
|
||||
canvas_invert_color(canvas);
|
||||
canvas_draw_str_aligned(canvas, 64, 61, AlignCenter, AlignBottom, lbl);
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
|
||||
/* TX overlay */
|
||||
if(m->is_transmitting) {
|
||||
canvas_draw_rbox(canvas, 24, 18, 80, 18, 3);
|
||||
canvas_invert_color(canvas);
|
||||
int wave = m->anim_frame % 3;
|
||||
canvas_draw_str(canvas, 28 + wave * 2, 25, ")))");
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str_aligned(canvas, 64, 24, AlignCenter, AlignCenter, "TX");
|
||||
canvas_invert_color(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Input ──────────────────────────────────────────────────────────────── */
|
||||
static bool subghz_car_emulate_view_input(InputEvent* event, void* context) {
|
||||
SubGhzCarEmulateView* instance = context;
|
||||
furi_assert(instance);
|
||||
|
||||
if(event->type == InputTypePress) {
|
||||
if(event->key == InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventCarEmulateExit, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Any directional / OK key → start TX */
|
||||
if(instance->callback) {
|
||||
/* Pack the raw InputKey into the upper bits of the event so the
|
||||
scene can read which button was pressed.
|
||||
Lower 16 bits = SubGhzCustomEventCarEmulateTransmit marker,
|
||||
upper 16 bits = InputKey value. */
|
||||
uint32_t ev = ((uint32_t)event->key << 16) |
|
||||
(uint32_t)SubGhzCustomEventCarEmulateTransmit;
|
||||
instance->callback(ev, instance->context);
|
||||
}
|
||||
return true;
|
||||
|
||||
} else if(event->type == InputTypeRelease) {
|
||||
if(event->key != InputKeyBack) {
|
||||
if(instance->callback) {
|
||||
instance->callback(SubGhzCustomEventCarEmulateStop, instance->context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* ── Alloc / Free ───────────────────────────────────────────────────────── */
|
||||
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void) {
|
||||
SubGhzCarEmulateView* instance = malloc(sizeof(SubGhzCarEmulateView));
|
||||
furi_check(instance);
|
||||
|
||||
instance->view = view_alloc();
|
||||
instance->callback = NULL;
|
||||
instance->context = NULL;
|
||||
|
||||
view_set_context(instance->view, instance);
|
||||
view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(SubGhzCarEmulateViewModel));
|
||||
view_set_draw_callback(instance->view, subghz_car_emulate_view_draw);
|
||||
view_set_input_callback(instance->view, subghz_car_emulate_view_input);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance) {
|
||||
furi_check(instance);
|
||||
view_free(instance->view);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance) {
|
||||
furi_check(instance);
|
||||
return instance->view;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_callback(
|
||||
SubGhzCarEmulateView* instance,
|
||||
SubGhzCarEmulateViewCallback callback,
|
||||
void* context) {
|
||||
furi_check(instance);
|
||||
instance->callback = callback;
|
||||
instance->context = context;
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_data(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* protocol_name,
|
||||
uint32_t serial,
|
||||
uint32_t counter,
|
||||
uint32_t original_counter,
|
||||
uint32_t freq,
|
||||
const char* preset,
|
||||
bool is_transmitting) {
|
||||
furi_check(instance);
|
||||
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzCarEmulateViewModel * m,
|
||||
{
|
||||
strncpy(m->protocol_name, protocol_name, sizeof(m->protocol_name) - 1);
|
||||
m->protocol_name[sizeof(m->protocol_name) - 1] = '\0';
|
||||
m->serial = serial;
|
||||
m->counter = counter;
|
||||
m->original_counter = original_counter;
|
||||
m->freq = freq;
|
||||
strncpy(m->preset, preset, sizeof(m->preset) - 1);
|
||||
m->preset[sizeof(m->preset) - 1] = '\0';
|
||||
m->is_transmitting = is_transmitting;
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
||||
void subghz_car_emulate_view_set_labels(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* ok,
|
||||
const char* up,
|
||||
const char* down,
|
||||
const char* left,
|
||||
const char* right) {
|
||||
furi_check(instance);
|
||||
with_view_model(
|
||||
instance->view,
|
||||
SubGhzCarEmulateViewModel * m,
|
||||
{
|
||||
strncpy(m->label_ok, ok ? ok : "", sizeof(m->label_ok) - 1);
|
||||
strncpy(m->label_up, up ? up : "", sizeof(m->label_up) - 1);
|
||||
strncpy(m->label_down, down ? down : "", sizeof(m->label_down) - 1);
|
||||
strncpy(m->label_left, left ? left : "", sizeof(m->label_left) - 1);
|
||||
strncpy(m->label_right, right ? right : "", sizeof(m->label_right) - 1);
|
||||
},
|
||||
true);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct SubGhzCarEmulateView SubGhzCarEmulateView;
|
||||
|
||||
typedef void (*SubGhzCarEmulateViewCallback)(uint32_t event, void* context);
|
||||
|
||||
SubGhzCarEmulateView* subghz_car_emulate_view_alloc(void);
|
||||
void subghz_car_emulate_view_free(SubGhzCarEmulateView* instance);
|
||||
View* subghz_car_emulate_view_get_view(SubGhzCarEmulateView* instance);
|
||||
|
||||
void subghz_car_emulate_view_set_callback(
|
||||
SubGhzCarEmulateView* instance,
|
||||
SubGhzCarEmulateViewCallback callback,
|
||||
void* context);
|
||||
|
||||
/** Update the fields shown on the view.
|
||||
* All strings are copied internally so the caller can free them after the call.
|
||||
*/
|
||||
void subghz_car_emulate_view_set_labels(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* ok,
|
||||
const char* up,
|
||||
const char* down,
|
||||
const char* left,
|
||||
const char* right);
|
||||
|
||||
void subghz_car_emulate_view_set_data(
|
||||
SubGhzCarEmulateView* instance,
|
||||
const char* protocol_name,
|
||||
uint32_t serial,
|
||||
uint32_t counter,
|
||||
uint32_t original_counter,
|
||||
uint32_t freq,
|
||||
const char* preset,
|
||||
bool is_transmitting);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,246 @@
|
||||
---
|
||||
Language: Cpp
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignArrayOfStructures: None
|
||||
AlignConsecutiveAssignments:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveBitFields:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: true
|
||||
AcrossComments: true
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveDeclarations:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCompound: false
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveMacros:
|
||||
Enabled: true
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: true
|
||||
AlignCompound: true
|
||||
AlignFunctionPointers: false
|
||||
PadOperators: true
|
||||
AlignConsecutiveShortCaseStatements:
|
||||
Enabled: false
|
||||
AcrossEmptyLines: false
|
||||
AcrossComments: false
|
||||
AlignCaseColons: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments:
|
||||
Kind: Never
|
||||
OverEmptyLines: 0
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowBreakBeforeNoexceptSpecifier: Never
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortCompoundRequirementOnASingleLine: true
|
||||
AllowShortEnumsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BitFieldColonSpacing: Both
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterExternBlock: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakAdjacentStringLiterals: true
|
||||
BreakAfterAttributes: Leave
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakArrays: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: Always
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInlineASMColon: OnlyMultiline
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 99
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: false
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
- M_EACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^(<|"(gtest|gmock|isl|json)/)'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '(Test)?$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentAccessModifiers: false
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentRequiresClause: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: true
|
||||
InsertBraces: false
|
||||
InsertNewlineAtEOF: true
|
||||
InsertTrailingCommas: None
|
||||
IntegerLiteralSeparator:
|
||||
Binary: 0
|
||||
BinaryMinDigits: 0
|
||||
Decimal: 0
|
||||
DecimalMinDigits: 0
|
||||
Hex: 0
|
||||
HexMinDigits: 0
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
KeepEmptyLinesAtEOF: false
|
||||
LambdaBodyIndentation: Signature
|
||||
LineEnding: DeriveLF
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 4
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: true
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PackConstructorInitializers: BinPack
|
||||
PenaltyBreakAssignment: 10
|
||||
PenaltyBreakBeforeFirstCallParameter: 30
|
||||
PenaltyBreakComment: 10
|
||||
PenaltyBreakFirstLessLess: 0
|
||||
PenaltyBreakOpenParenthesis: 0
|
||||
PenaltyBreakScopeResolution: 500
|
||||
PenaltyBreakString: 10
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 100
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PointerAlignment: Left
|
||||
PPIndentWidth: -1
|
||||
QualifierAlignment: Leave
|
||||
ReferenceAlignment: Pointer
|
||||
ReflowComments: false
|
||||
RemoveBracesLLVM: false
|
||||
RemoveParentheses: Leave
|
||||
RemoveSemicolon: true
|
||||
RequiresClausePosition: OwnLine
|
||||
RequiresExpressionIndentation: OuterScope
|
||||
SeparateDefinitionBlocks: Leave
|
||||
ShortNamespaceLines: 1
|
||||
SkipMacroDefinitionBody: false
|
||||
SortIncludes: Never
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: Never
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeJsonColon: false
|
||||
SpaceBeforeParens: Never
|
||||
SpaceBeforeParensOptions:
|
||||
AfterControlStatements: false
|
||||
AfterForeachMacros: false
|
||||
AfterFunctionDefinitionName: false
|
||||
AfterFunctionDeclarationName: false
|
||||
AfterIfMacros: false
|
||||
AfterOverloadedOperator: false
|
||||
AfterPlacementOperator: true
|
||||
AfterRequiresInClause: false
|
||||
AfterRequiresInExpression: false
|
||||
BeforeNonEmptyParentheses: false
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParens: Never
|
||||
SpacesInParensOptions:
|
||||
InCStyleCasts: false
|
||||
InConditionalStatements: false
|
||||
InEmptyParentheses: false
|
||||
Other: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: c++20
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
VerilogBreakBetweenInstancePorts: true
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
||||
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
@@ -0,0 +1,109 @@
|
||||
# **ProtoPirate**
|
||||
|
||||
### _for Flipper Zero_
|
||||
|
||||
## **⚠️ Warning: Important Security & Project Update**
|
||||
Read message by following link below:
|
||||
|
||||
https://protopirate.net/ProtoPirate
|
||||
|
||||
Main repo is located at: https://protopirate.net/ProtoPirate/ProtoPirate
|
||||
|
||||
All others are read only mirrors!
|
||||
|
||||
|
||||
ProtoPirate is an experimental rolling-code analysis toolkit developed by members of **The Pirates' Plunder**.
|
||||
|
||||
The app currently supports decoding for multiple automotive key-fob families (Kia, Ford, Subaru, Suzuki, VW, and more), with the goal of being a drop-in Flipper app (.fap) that is free, open source, and can be used on any Flipper Zero firmware.
|
||||
|
||||
App is intended for educational and security purposes only, and has no signal transmission enabled by default. This prevents users from accidentally desyncing their keyfobs, making it safe for non-specialists.
|
||||
|
||||
## **Supported Protocols**
|
||||
|
||||
| Protocol | Decoder | Encoder | Signal Encoding | Modulation | Encryption | CRC | Frequency |
|
||||
|:------------------------------|:--------|:--------|:--------|:--------|:--------|:--------|:--------|
|
||||
| Fiat V0 | ✅ | ✅ | Manchester | AM | Rolling Code (Static Emu only) | ❌ | 433.92 |
|
||||
| Fiat V1 | ✅ | ❌ | Manchester | AM | Rolling Code | ❌ | 433.92 |
|
||||
| Ford V0 | ✅ | ✅ | Manchester | AM | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
|
||||
| Kia V0 | ✅ | ✅ | PWM | FM | Rolling Code | CRC8 | 433.92 |
|
||||
| Kia V1 | ✅ | ✅ | Manchester | AM | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V2 | ✅ | ✅ | Manchester | AM/FM | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V3 / V4 | ✅ | ✅ | PWM | FM | KeeLoq | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V5 | ✅ | ❌ | Manchester | FM | Rolling Code | ✅ | 433.92 |
|
||||
| Kia V6 | ✅ | ✅ | Manchester | FM | AES128 | CRC8 | 433.92 |
|
||||
| Kia V7 | ✅ | ✅ | Manchester | FM | Rolling Code | CRC8 | 433.92 |
|
||||
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM/FM | XTEA/XOR | ✅ | 433.92 |
|
||||
| Scher-Khan | ✅ | ❌ | PWM | FM | Magic Code | ❌ | 433.92 |
|
||||
| StarLine | ✅ | ✅ | PWM | AM | KeeLoq | ❌ | 433.92 |
|
||||
| Subaru | ✅ | ✅ | PWM | AM | Rolling Code | ❌ | 433.92 |
|
||||
| Suzuki | ✅ | ✅ | PWM | FM | Rolling Code | CRC8 | 433.92 |
|
||||
| VAG (VW/Audi/Seat/Skoda) | ✅ | ✅ | Manchester | AM | AUT64/XTEA | ❌ | 434.42 |
|
||||
| Mazda V0 | ✅ | ✅ | Manchester | AM | Rolling Code | ❌ | 433.92 |
|
||||
| Mitsubishi V0 | ✅ | ❌ | PWM | AM | Rolling Code | ❌ | 433.92 |
|
||||
| Porsche/Touareg | ✅ | ❌ | PWM | AM | Rolling Code | ❌ | 433.92 |
|
||||
|
||||
_More Coming Soon_
|
||||
|
||||
## **Features**
|
||||
|
||||
### 📡 Protocol Receiver
|
||||
|
||||
Real-time signal capture and decoding with animated radar display. Supports frequency hopping.
|
||||
|
||||
### 📂 Sub Decode
|
||||
|
||||
Load and analyze existing `.sub` files from your SD card. Browse `/ext/subghz/` to decode previously captured signals.
|
||||
|
||||
### ⏱️ Timing Tuner
|
||||
|
||||
Tool for protocol developers to compare real fob signal timing against protocol definitions.
|
||||
|
||||
- **Protocol Definition**: Expected short/long pulse durations and tolerance
|
||||
- **Received Signal**: Measured timing from real fob (avg, min, max, sample count)
|
||||
- **Analysis**: Difference from expected, jitter measurements
|
||||
- **Conclusion**: Whether timing matches or needs adjustment with specific recommendations
|
||||
|
||||
## **Credits**
|
||||
|
||||
The following contributors are recognized for helping us keep open sourced projects and the freeware community alive.
|
||||
|
||||
### **App Development**
|
||||
|
||||
- RocketGod
|
||||
- MMX
|
||||
- Leeroy
|
||||
- gullradriel
|
||||
- Skorp - Thanks, I sneaked a lot from Weather App!
|
||||
- Vadim's Radio Driver
|
||||
|
||||
### **Protocol Magic**
|
||||
|
||||
- L0rdDiakon
|
||||
- YougZ
|
||||
- RocketGod
|
||||
- MMX
|
||||
- DoobTheGoober
|
||||
- Skorp
|
||||
- Slackware
|
||||
- Trikk
|
||||
- Wootini
|
||||
- Li0ard
|
||||
- Leeroy
|
||||
|
||||
### **Reverse Engineering Support**
|
||||
|
||||
- DoobTheGoober
|
||||
- MMX
|
||||
- NeedNotApply
|
||||
- RocketGod
|
||||
- Slackware
|
||||
- Trikk
|
||||
- Li0ard
|
||||
|
||||
## **Community & Support**
|
||||
|
||||
Join **The Pirates' Plunder** on Discord for development updates, testing, protocol research, community support, and a bunch of badasses doing fun shit:
|
||||
|
||||
➡️ **[https://discord.gg/thepirates](https://discord.gg/thepirates)**
|
||||
|
||||
<img alt="rocketgod_logo_transparent" src="https://github.com/user-attachments/assets/ad15b106-152c-4a60-a9e2-4d40dfa8f3c6" />
|
||||
@@ -0,0 +1,97 @@
|
||||
App(
|
||||
appid="proto_pirate",
|
||||
name="ProtoPirate",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
targets=["f7"],
|
||||
entry_point="protopirate_app",
|
||||
requires=["gui"],
|
||||
stack_size=2 * 1024,
|
||||
sources=["*.c*"],
|
||||
fap_description="Decode car key fob signals from Sub-GHz",
|
||||
fap_version="3.0",
|
||||
fap_icon="images/protopirate_10px.png",
|
||||
fap_category="Sub-GHz",
|
||||
fap_icon_assets="images",
|
||||
fap_file_assets="keystore",
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_am_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_am_plugin_ep",
|
||||
requires=["proto_pirate"],
|
||||
sources=[
|
||||
"protocols/plugins/protopirate_am_plugin.c",
|
||||
"protocols/protocols_common.c",
|
||||
"protocols/aut64.c",
|
||||
"protocols/chrysler_v0.c",
|
||||
"protocols/fiat_v0.c",
|
||||
"protocols/fiat_v1.c",
|
||||
"protocols/ford_v0.c",
|
||||
"protocols/kia_v1.c",
|
||||
"protocols/porsche_touareg.c",
|
||||
"protocols/psa.c",
|
||||
"protocols/psa_crypto.c",
|
||||
"protocols/subaru.c",
|
||||
"protocols/vag.c",
|
||||
"protocols/star_line.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_fm_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_fm_plugin_ep",
|
||||
requires=["proto_pirate"],
|
||||
sources=[
|
||||
"protocols/plugins/protopirate_fm_plugin.c",
|
||||
"protocols/protocols_common.c",
|
||||
"protocols/keys.c",
|
||||
"protocols/scher_khan.c",
|
||||
"protocols/kia_v0.c",
|
||||
"protocols/kia_v2.c",
|
||||
"protocols/kia_v3_v4.c",
|
||||
"protocols/kia_v5.c",
|
||||
"protocols/kia_v6.c",
|
||||
"protocols/kia_v7.c",
|
||||
"protocols/ford_v1.c",
|
||||
"protocols/ford_v2.c",
|
||||
"protocols/ford_v3.c",
|
||||
"protocols/honda_static.c",
|
||||
"protocols/land_rover_v0.c",
|
||||
"protocols/mazda_v0.c",
|
||||
"protocols/mitsubishi_v0.c",
|
||||
"protocols/psa.c",
|
||||
"protocols/psa_crypto.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_emulate_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_emulate_plugin_ep",
|
||||
requires=["proto_pirate"],
|
||||
sources=[
|
||||
"scenes/plugins/protopirate_emulate_plugin.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
App(
|
||||
appid="protopirate_psa_bf_plugin",
|
||||
apptype=FlipperAppType.PLUGIN,
|
||||
entry_point="protopirate_psa_bf_plugin_ep",
|
||||
requires=["proto_pirate"],
|
||||
cdefines=["PROTOPIRATE_PSA_BF_PLUGIN_BUILD=1"],
|
||||
sources=[
|
||||
"scenes/plugins/protopirate_psa_bf_plugin.c",
|
||||
"protocols/psa_crypto.c",
|
||||
"protocols/psa_crypto_bf.c",
|
||||
"protocols/psa_bf_core.c",
|
||||
"protocols/protocols_common.c",
|
||||
],
|
||||
fap_icon_assets="images",
|
||||
fal_embedded=True,
|
||||
)
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
//#define ENABLE_TIMING_TUNER_SCENE
|
||||
//#define ENABLE_SUB_DECODE_SCENE
|
||||
|
||||
#define ENABLE_EMULATE_FEATURE
|
||||
|
||||
#define REMOVE_LOGS
|
||||
|
||||
#ifdef REMOVE_LOGS
|
||||
// Undefine existing macros
|
||||
#undef FURI_LOG_E
|
||||
#undef FURI_LOG_W
|
||||
#undef FURI_LOG_I
|
||||
#undef FURI_LOG_D
|
||||
#undef FURI_LOG_T
|
||||
// Define empty macros
|
||||
#define FURI_LOG_E(tag, format, ...)
|
||||
#define FURI_LOG_W(tag, format, ...)
|
||||
#define FURI_LOG_I(tag, format, ...)
|
||||
#define FURI_LOG_D(tag, format, ...)
|
||||
#define FURI_LOG_T(tag, format, ...)
|
||||
|
||||
#endif // REMOVE_LOGS
|
||||
@@ -0,0 +1,193 @@
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "protopirate_psa_bf_host.h"
|
||||
#include "../protopirate_history.h"
|
||||
#include "../protocols/protocols_common.h"
|
||||
#include "../scenes/plugins/protopirate_psa_bf_plugin.h"
|
||||
|
||||
#include <loader/firmware_api/firmware_api.h>
|
||||
#include <lib/flipper_application/plugins/plugin_manager.h>
|
||||
#include <lib/flipper_application/plugins/composite_resolver.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "ProtoPiratePsaBfHost"
|
||||
#define PSA_BF_PLUGIN_PATH APP_ASSETS_PATH("plugins/protopirate_psa_bf_plugin.fal")
|
||||
|
||||
static bool host_ensure_widget(void* app) {
|
||||
return protopirate_ensure_widget((ProtoPirateApp*)app);
|
||||
}
|
||||
|
||||
static Widget* host_get_widget(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
return a ? a->widget : NULL;
|
||||
}
|
||||
|
||||
static FlipperFormat* host_get_history_flipper_format(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(!a || !a->txrx || !a->txrx->history) return NULL;
|
||||
return protopirate_history_get_raw_data(a->txrx->history, a->txrx->idx_menu_chosen);
|
||||
}
|
||||
|
||||
static uint16_t host_get_history_index(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
return a && a->txrx ? a->txrx->idx_menu_chosen : 0;
|
||||
}
|
||||
|
||||
static void host_set_history_index(void* app, uint16_t idx) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(a && a->txrx) a->txrx->idx_menu_chosen = idx;
|
||||
}
|
||||
|
||||
static ProtoPirateHistory* host_get_history(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
return a && a->txrx ? a->txrx->history : NULL;
|
||||
}
|
||||
|
||||
static void host_history_set_item_str(void* app, uint16_t idx, const char* str) {
|
||||
ProtoPirateHistory* history = host_get_history(app);
|
||||
if(history) protopirate_history_set_item_str(history, idx, str);
|
||||
}
|
||||
|
||||
static void host_patch_flipper_format_on_success(FlipperFormat* ff, const PsaBfState* s) {
|
||||
if(!ff || !s) return;
|
||||
flipper_format_rewind(ff);
|
||||
flipper_format_insert_or_update_uint32(ff, FF_SERIAL, &s->decrypted_serial, 1);
|
||||
uint32_t btn = s->decrypted_button;
|
||||
flipper_format_insert_or_update_uint32(ff, FF_BTN, &btn, 1);
|
||||
flipper_format_insert_or_update_uint32(ff, FF_CNT, &s->decrypted_counter, 1);
|
||||
uint32_t type = s->decrypted_type;
|
||||
flipper_format_insert_or_update_uint32(ff, FF_TYPE, &type, 1);
|
||||
uint32_t crc_val = s->decrypted_crc;
|
||||
flipper_format_insert_or_update_uint32(ff, "CRC", &crc_val, 1);
|
||||
flipper_format_insert_or_update_uint32(ff, "Seed", &s->decrypted_seed, 1);
|
||||
}
|
||||
|
||||
static void host_send_custom_event(void* app, uint32_t event) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(a) view_dispatcher_send_custom_event(a->view_dispatcher, event);
|
||||
}
|
||||
|
||||
static void host_notification_error(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(a) notification_message(a->notifications, &sequence_error);
|
||||
}
|
||||
|
||||
static void host_notification_success(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(a) notification_message(a->notifications, &sequence_success);
|
||||
}
|
||||
|
||||
static void host_receiver_info_rebuild_widget(void* app) {
|
||||
protopirate_receiver_info_rebuild_normal_widget(app);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_SUB_DECODE_SCENE
|
||||
static void host_subdecode_signal_info_refresh(void* app) {
|
||||
protopirate_subdecode_psa_bf_complete_refresh(app);
|
||||
}
|
||||
#else
|
||||
static void host_subdecode_signal_info_refresh(void* app) {
|
||||
UNUSED(app);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void host_scene_previous(void* app) {
|
||||
ProtoPirateApp* a = (ProtoPirateApp*)app;
|
||||
if(a) scene_manager_previous_scene(a->scene_manager);
|
||||
}
|
||||
|
||||
static const ProtoPiratePsaBfHostApi protopirate_psa_bf_host_api = {
|
||||
.ensure_widget = host_ensure_widget,
|
||||
.get_widget = host_get_widget,
|
||||
.get_history_flipper_format = host_get_history_flipper_format,
|
||||
.get_history_index = host_get_history_index,
|
||||
.set_history_index = host_set_history_index,
|
||||
.get_history = host_get_history,
|
||||
.history_set_item_str = host_history_set_item_str,
|
||||
.patch_flipper_format_on_success = host_patch_flipper_format_on_success,
|
||||
.send_custom_event = host_send_custom_event,
|
||||
.notification_error = host_notification_error,
|
||||
.notification_success = host_notification_success,
|
||||
.receiver_info_rebuild_widget = host_receiver_info_rebuild_widget,
|
||||
.subdecode_signal_info_refresh = host_subdecode_signal_info_refresh,
|
||||
.scene_previous = host_scene_previous,
|
||||
};
|
||||
|
||||
static void psa_bf_plugin_unload(ProtoPirateApp* app) {
|
||||
furi_check(app);
|
||||
app->psa_bf_plugin = NULL;
|
||||
|
||||
if(app->psa_bf_plugin_manager) {
|
||||
plugin_manager_free(app->psa_bf_plugin_manager);
|
||||
app->psa_bf_plugin_manager = NULL;
|
||||
}
|
||||
|
||||
if(app->psa_bf_plugin_resolver) {
|
||||
composite_api_resolver_free(app->psa_bf_plugin_resolver);
|
||||
app->psa_bf_plugin_resolver = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app) {
|
||||
furi_check(app);
|
||||
|
||||
if(app->psa_bf_plugin) return true;
|
||||
|
||||
if(app->psa_bf_plugin_manager || app->psa_bf_plugin_resolver) {
|
||||
psa_bf_plugin_unload(app);
|
||||
}
|
||||
|
||||
CompositeApiResolver* resolver = composite_api_resolver_alloc();
|
||||
if(!resolver) {
|
||||
FURI_LOG_E(TAG, "Failed to allocate PSA BF plugin resolver");
|
||||
return false;
|
||||
}
|
||||
composite_api_resolver_add(resolver, firmware_api_interface);
|
||||
|
||||
PluginManager* manager = plugin_manager_alloc(
|
||||
PROTOPIRATE_PSA_BF_PLUGIN_APP_ID,
|
||||
PROTOPIRATE_PSA_BF_PLUGIN_API_VERSION,
|
||||
composite_api_resolver_get(resolver));
|
||||
if(!manager) {
|
||||
FURI_LOG_E(TAG, "Failed to allocate PSA BF plugin manager");
|
||||
composite_api_resolver_free(resolver);
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginManagerError error = plugin_manager_load_single(manager, PSA_BF_PLUGIN_PATH);
|
||||
if(error != PluginManagerErrorNone) {
|
||||
FURI_LOG_E(TAG, "Failed to load PSA BF plugin %s: %d", PSA_BF_PLUGIN_PATH, (int)error);
|
||||
plugin_manager_free(manager);
|
||||
composite_api_resolver_free(resolver);
|
||||
return false;
|
||||
}
|
||||
|
||||
const ProtoPiratePsaBfPlugin* plugin = plugin_manager_get_ep(manager, 0U);
|
||||
if(!plugin || !plugin->set_host_api || !plugin->needs_bruteforce || !plugin->on_scene_event) {
|
||||
FURI_LOG_E(TAG, "PSA BF plugin entry point is invalid");
|
||||
plugin_manager_free(manager);
|
||||
composite_api_resolver_free(resolver);
|
||||
return false;
|
||||
}
|
||||
|
||||
app->psa_bf_plugin_resolver = resolver;
|
||||
app->psa_bf_plugin_manager = manager;
|
||||
app->psa_bf_plugin = plugin;
|
||||
plugin->set_host_api(&protopirate_psa_bf_host_api);
|
||||
return true;
|
||||
}
|
||||
|
||||
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app) {
|
||||
if(!app) return;
|
||||
if(app->psa_bf_plugin && app->psa_bf_plugin->is_running && app->psa_bf_plugin->is_running(app)) {
|
||||
return;
|
||||
}
|
||||
psa_bf_plugin_unload(app);
|
||||
}
|
||||
|
||||
void protopirate_psa_bf_context_release(ProtoPirateApp* app) {
|
||||
if(!app) return;
|
||||
if(app->psa_bf_plugin && app->psa_bf_plugin->context_release) {
|
||||
app->psa_bf_plugin->context_release(app);
|
||||
}
|
||||
psa_bf_plugin_unload(app);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct ProtoPirateApp ProtoPirateApp;
|
||||
|
||||
bool protopirate_psa_bf_plugin_ensure_loaded(ProtoPirateApp* app);
|
||||
void protopirate_psa_bf_plugin_unload_if_idle(ProtoPirateApp* app);
|
||||
void protopirate_psa_bf_context_release(ProtoPirateApp* app);
|
||||
|
||||
void protopirate_receiver_info_rebuild_normal_widget(void* app);
|
||||
|
||||
#ifdef ENABLE_SUB_DECODE_SCENE
|
||||
void protopirate_subdecode_psa_bf_complete_refresh(void* app);
|
||||
#endif
|
||||
@@ -0,0 +1,186 @@
|
||||
// helpers/protopirate_settings.c
|
||||
#include "protopirate_settings.h"
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
#include "../defines.h"
|
||||
#include "../protocols/protocols_common.h"
|
||||
|
||||
#define TAG "ProtoPirateSettings"
|
||||
|
||||
#define SETTINGS_FILE_HEADER "ProtoPirate Settings"
|
||||
#define SETTINGS_FILE_VERSION 1
|
||||
|
||||
void protopirate_settings_set_defaults(ProtoPirateSettings* settings) {
|
||||
settings->frequency = 433920000;
|
||||
settings->preset_index = 0;
|
||||
settings->tx_power = 0;
|
||||
settings->auto_save = false;
|
||||
settings->hopping_enabled = false;
|
||||
settings->emulate_feature_enabled = false;
|
||||
}
|
||||
|
||||
void protopirate_settings_load(ProtoPirateSettings* settings) {
|
||||
// Set defaults first
|
||||
protopirate_settings_set_defaults(settings);
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_existing(ff, PROTOPIRATE_SETTINGS_FILE)) {
|
||||
FURI_LOG_I(TAG, "Settings file not found, using defaults");
|
||||
break;
|
||||
}
|
||||
|
||||
FuriString* header = furi_string_alloc();
|
||||
uint32_t version = 0;
|
||||
|
||||
if(!flipper_format_read_header(ff, header, &version)) {
|
||||
FURI_LOG_W(TAG, "Failed to read settings header");
|
||||
furi_string_free(header);
|
||||
break;
|
||||
}
|
||||
|
||||
if(version != SETTINGS_FILE_VERSION) {
|
||||
FURI_LOG_W(TAG, "Unsupported settings version %lu", (unsigned long)version);
|
||||
furi_string_free(header);
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(header, SETTINGS_FILE_HEADER) != 0) {
|
||||
FURI_LOG_W(TAG, "Invalid settings file header");
|
||||
furi_string_free(header);
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_free(header);
|
||||
|
||||
// Read frequency
|
||||
if(!flipper_format_read_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
|
||||
FURI_LOG_W(TAG, "Failed to read frequency, using default");
|
||||
settings->frequency = 433920000;
|
||||
}
|
||||
|
||||
// Read preset index
|
||||
uint32_t preset_temp = 0;
|
||||
if(!flipper_format_read_uint32(ff, "PresetIndex", &preset_temp, 1)) {
|
||||
FURI_LOG_W(TAG, "Failed to read preset index, using default");
|
||||
preset_temp = 0;
|
||||
}
|
||||
settings->preset_index = (uint8_t)preset_temp;
|
||||
|
||||
// Read auto-save
|
||||
uint32_t auto_save_temp = 0;
|
||||
if(!flipper_format_read_uint32(ff, "AutoSave", &auto_save_temp, 1)) {
|
||||
FURI_LOG_W(TAG, "Failed to read auto-save, using default");
|
||||
auto_save_temp = 0;
|
||||
}
|
||||
settings->auto_save = (auto_save_temp == 1);
|
||||
|
||||
// Read tx-power
|
||||
uint32_t tx_power_temp = 0;
|
||||
if(!flipper_format_read_uint32(ff, "TXPower", &tx_power_temp, 1)) {
|
||||
FURI_LOG_W(TAG, "Failed to read TXPower, using default");
|
||||
tx_power_temp = 0;
|
||||
}
|
||||
settings->tx_power = (uint8_t)tx_power_temp;
|
||||
|
||||
// Read hopping
|
||||
uint32_t hopping_temp = 0;
|
||||
if(!flipper_format_read_uint32(ff, "Hopping", &hopping_temp, 1)) {
|
||||
FURI_LOG_W(TAG, "Failed to read hopping, using default");
|
||||
hopping_temp = 0;
|
||||
}
|
||||
settings->hopping_enabled = (hopping_temp == 1);
|
||||
|
||||
uint32_t emulate_temp = 0;
|
||||
if(!flipper_format_read_uint32(ff, "EmulateFeature", &emulate_temp, 1)) {
|
||||
FURI_LOG_I(TAG, "EmulateFeature key missing, defaulting to disabled");
|
||||
emulate_temp = 0;
|
||||
}
|
||||
settings->emulate_feature_enabled = (emulate_temp == 1);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Settings loaded: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
|
||||
settings->frequency,
|
||||
settings->preset_index,
|
||||
settings->auto_save,
|
||||
settings->hopping_enabled,
|
||||
settings->emulate_feature_enabled);
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void protopirate_settings_save(ProtoPirateSettings* settings) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
// Ensure directory exists
|
||||
storage_simply_mkdir(storage, PROTOPIRATE_SETTINGS_DIR);
|
||||
|
||||
FlipperFormat* ff = flipper_format_file_alloc(storage);
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_always(ff, PROTOPIRATE_SETTINGS_FILE)) {
|
||||
FURI_LOG_E(TAG, "Failed to open settings file for writing");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_header_cstr(ff, SETTINGS_FILE_HEADER, SETTINGS_FILE_VERSION)) {
|
||||
FURI_LOG_E(TAG, "Failed to write settings header");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_uint32(ff, FF_FREQUENCY, &settings->frequency, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write frequency");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t preset_temp = settings->preset_index;
|
||||
if(!flipper_format_write_uint32(ff, "PresetIndex", &preset_temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write preset index");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t auto_save_temp = settings->auto_save ? 1 : 0;
|
||||
if(!flipper_format_write_uint32(ff, "AutoSave", &auto_save_temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write auto-save");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t tx_power_temp = settings->tx_power;
|
||||
if(!flipper_format_write_uint32(ff, "TXPower", &tx_power_temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write TX Power");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t hopping_temp = settings->hopping_enabled ? 1 : 0;
|
||||
if(!flipper_format_write_uint32(ff, "Hopping", &hopping_temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write hopping");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t emulate_temp = settings->emulate_feature_enabled ? 1 : 0;
|
||||
if(!flipper_format_write_uint32(ff, "EmulateFeature", &emulate_temp, 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write emulate feature flag");
|
||||
break;
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Settings saved: freq=%lu, preset=%u, auto_save=%d, hopping=%d, emulate=%d",
|
||||
settings->frequency,
|
||||
settings->preset_index,
|
||||
settings->auto_save,
|
||||
settings->hopping_enabled,
|
||||
settings->emulate_feature_enabled);
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(ff);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// helpers/protopirate_settings.h
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define PROTOPIRATE_SETTINGS_FILE APP_DATA_PATH("settings.txt")
|
||||
#define PROTOPIRATE_SETTINGS_DIR APP_DATA_PATH()
|
||||
|
||||
typedef struct {
|
||||
uint32_t frequency;
|
||||
uint8_t preset_index;
|
||||
uint8_t tx_power;
|
||||
bool auto_save;
|
||||
bool hopping_enabled;
|
||||
bool emulate_feature_enabled;
|
||||
} ProtoPirateSettings;
|
||||
|
||||
void protopirate_settings_load(ProtoPirateSettings* settings);
|
||||
void protopirate_settings_save(ProtoPirateSettings* settings);
|
||||
void protopirate_settings_set_defaults(ProtoPirateSettings* settings);
|
||||
@@ -0,0 +1,555 @@
|
||||
// helpers/protopirate_storage.c
|
||||
#include "protopirate_storage.h"
|
||||
#include "../defines.h"
|
||||
#include "../protocols/protocols_common.h"
|
||||
|
||||
#define TAG "ProtoPirateStorage"
|
||||
|
||||
bool protopirate_storage_init(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool result = storage_simply_mkdir(storage, PROTOPIRATE_APP_FOLDER);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void protopirate_storage_wipe_history_cache(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
|
||||
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
FURI_LOG_I(TAG, "Wiped history cache");
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
void protopirate_storage_purge_temp_history_at_startup(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_dir_exists(storage, PROTOPIRATE_HISTORY_FOLDER)) {
|
||||
storage_simply_remove_recursive(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool protopirate_storage_ensure_history_folder(void) {
|
||||
if(!protopirate_storage_init()) {
|
||||
return false;
|
||||
}
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
storage_simply_mkdir(storage, PROTOPIRATE_CACHE_FOLDER);
|
||||
bool ok = storage_simply_mkdir(storage, PROTOPIRATE_HISTORY_FOLDER);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out) {
|
||||
furi_check(out);
|
||||
furi_string_printf(
|
||||
out,
|
||||
"%s/hist_%08lu%s",
|
||||
PROTOPIRATE_HISTORY_FOLDER,
|
||||
(unsigned long)seq,
|
||||
PROTOPIRATE_APP_EXTENSION);
|
||||
}
|
||||
|
||||
bool protopirate_storage_save_history_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t seq,
|
||||
FuriString* out_path) {
|
||||
furi_check(flipper_format);
|
||||
furi_check(out_path);
|
||||
|
||||
if(!protopirate_storage_ensure_history_folder()) {
|
||||
FURI_LOG_E(TAG, "History folder missing");
|
||||
return false;
|
||||
}
|
||||
|
||||
protopirate_storage_build_history_path(seq, out_path);
|
||||
|
||||
return protopirate_storage_save_capture_to_path(
|
||||
flipper_format, furi_string_get_cstr(out_path));
|
||||
}
|
||||
|
||||
static void sanitize_filename(const char* input, char* output, size_t output_size) {
|
||||
if(!output || output_size == 0) return;
|
||||
if(!input) {
|
||||
output[0] = '\0';
|
||||
return;
|
||||
}
|
||||
size_t i = 0;
|
||||
size_t j = 0;
|
||||
while(input[i] != '\0' && j < output_size - 1) {
|
||||
char c = input[i];
|
||||
if(c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' ||
|
||||
c == '>' || c == '|' || c == ' ') {
|
||||
output[j] = '_';
|
||||
} else {
|
||||
output[j] = c;
|
||||
}
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
output[j] = '\0';
|
||||
}
|
||||
|
||||
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename) {
|
||||
if(!protocol_name || !out_filename) return false;
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FuriString* temp_path = furi_string_alloc();
|
||||
uint32_t index = 0;
|
||||
bool found = false;
|
||||
|
||||
char safe_name[64];
|
||||
sanitize_filename(protocol_name, safe_name, sizeof(safe_name));
|
||||
|
||||
while(!found && index <= 999) {
|
||||
furi_string_printf(
|
||||
temp_path,
|
||||
"%s/%s_%03lu%s",
|
||||
PROTOPIRATE_APP_FOLDER,
|
||||
safe_name,
|
||||
(unsigned long)index,
|
||||
PROTOPIRATE_APP_EXTENSION);
|
||||
|
||||
if(!storage_file_exists(storage, furi_string_get_cstr(temp_path))) {
|
||||
furi_string_set(out_filename, temp_path);
|
||||
found = true;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_free(temp_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return found;
|
||||
}
|
||||
|
||||
static const char* const protopirate_storage_base_u32_fields[] = {
|
||||
"TE",
|
||||
FF_SERIAL,
|
||||
FF_BTN,
|
||||
"BtnSig",
|
||||
FF_CNT,
|
||||
"Extra",
|
||||
"ExtraBit",
|
||||
"Extra_bits",
|
||||
"Tail",
|
||||
"Checksum",
|
||||
"CRC",
|
||||
FF_TYPE,
|
||||
};
|
||||
|
||||
static const char* const protopirate_storage_tail_u32_fields[] = {
|
||||
"DataHi",
|
||||
"DataLo",
|
||||
"RawCnt",
|
||||
"Encrypted",
|
||||
"Decrypted",
|
||||
"KIAVersion",
|
||||
"Checksum",
|
||||
};
|
||||
|
||||
static bool protopirate_storage_fail(const char* action, const char* key) {
|
||||
UNUSED(action);
|
||||
UNUSED(key);
|
||||
FURI_LOG_E(TAG, "%s failed: %s", action, key);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
protopirate_storage_get_count(FlipperFormat* flipper_format, const char* key, uint32_t* count) {
|
||||
*count = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
return flipper_format_get_value_count(flipper_format, key, count) && (*count > 0);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_string_optional(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
FuriString* value) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, key, value)) {
|
||||
return true;
|
||||
}
|
||||
if(!flipper_format_write_string(save_file, key, value)) {
|
||||
return protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_string_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
FuriString* value) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
|
||||
return true;
|
||||
}
|
||||
if(!flipper_format_read_string(flipper_format, key, value)) {
|
||||
return protopirate_storage_fail("Read", key);
|
||||
}
|
||||
if(!flipper_format_write_string(save_file, key, value)) {
|
||||
return protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_u32_optional(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key) {
|
||||
uint32_t value = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, key, &value, 1)) {
|
||||
return true;
|
||||
}
|
||||
if(!flipper_format_write_uint32(save_file, key, &value, 1)) {
|
||||
return protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_u32_fields(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* const* fields,
|
||||
size_t field_count) {
|
||||
for(size_t i = 0; i < field_count; i++) {
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, fields[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_hex_fixed(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
size_t len,
|
||||
bool* copied) {
|
||||
uint8_t data[8];
|
||||
furi_check(len <= sizeof(data));
|
||||
if(copied) {
|
||||
*copied = false;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_hex(flipper_format, key, data, len)) {
|
||||
return true;
|
||||
}
|
||||
if(copied) {
|
||||
*copied = true;
|
||||
}
|
||||
if(!flipper_format_write_hex(save_file, key, data, len)) {
|
||||
return protopirate_storage_fail("Write", key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_u32_array(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t count,
|
||||
uint32_t max_count) {
|
||||
if(count >= max_count) {
|
||||
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t* data = malloc(sizeof(uint32_t) * count);
|
||||
if(!data) {
|
||||
FURI_LOG_E(TAG, "Malloc failed: %s (%lu u32)", key, (unsigned long)count);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = false;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, key, data, count)) {
|
||||
protopirate_storage_fail("Read", key);
|
||||
} else if(!flipper_format_write_uint32(save_file, key, data, count)) {
|
||||
protopirate_storage_fail("Write", key);
|
||||
} else {
|
||||
status = true;
|
||||
}
|
||||
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_u32_array_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t max_count) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
|
||||
return true;
|
||||
}
|
||||
return protopirate_storage_copy_u32_array(save_file, flipper_format, key, count, max_count);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_hex_array_if_present(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t max_count) {
|
||||
uint32_t count = 0;
|
||||
if(!protopirate_storage_get_count(flipper_format, key, &count)) {
|
||||
return true;
|
||||
}
|
||||
if(count >= max_count) {
|
||||
FURI_LOG_E(TAG, "%s too large: %lu", key, (unsigned long)count);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* data = malloc(count);
|
||||
if(!data) {
|
||||
FURI_LOG_E(TAG, "Malloc failed: %s (%lu bytes)", key, (unsigned long)count);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = false;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_hex(flipper_format, key, data, count)) {
|
||||
protopirate_storage_fail("Read", key);
|
||||
} else if(!flipper_format_write_hex(save_file, key, data, count)) {
|
||||
protopirate_storage_fail("Write", key);
|
||||
} else {
|
||||
status = true;
|
||||
}
|
||||
|
||||
free(data);
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_key(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* value) {
|
||||
uint32_t count = 0;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_string(flipper_format, FF_KEY, value)) {
|
||||
if(!flipper_format_write_string(save_file, FF_KEY, value)) {
|
||||
return protopirate_storage_fail("Write", FF_KEY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if(protopirate_storage_get_count(flipper_format, FF_KEY, &count)) {
|
||||
return protopirate_storage_copy_u32_array(save_file, flipper_format, FF_KEY, count, 1024);
|
||||
}
|
||||
|
||||
return protopirate_storage_copy_hex_fixed(save_file, flipper_format, FF_KEY, 8, NULL);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_hex_or_u32(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
size_t hex_len) {
|
||||
bool copied = false;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, key, hex_len, &copied)) {
|
||||
return false;
|
||||
}
|
||||
return copied || protopirate_storage_copy_u32_optional(save_file, flipper_format, key);
|
||||
}
|
||||
|
||||
static bool protopirate_storage_copy_key_2(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* value) {
|
||||
bool copied = false;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key_2", 8, &copied)) {
|
||||
return false;
|
||||
}
|
||||
if(copied) {
|
||||
return true;
|
||||
}
|
||||
return protopirate_storage_copy_string_optional(save_file, flipper_format, "Key_2", value) &&
|
||||
protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_2");
|
||||
}
|
||||
|
||||
static bool protopirate_storage_write_capture_data(
|
||||
FlipperFormat* save_file,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(save_file);
|
||||
furi_check(flipper_format);
|
||||
|
||||
FuriString* string_value = furi_string_alloc();
|
||||
if(!string_value) {
|
||||
FURI_LOG_E(TAG, "Failed to alloc string_value");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = false;
|
||||
do {
|
||||
if(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, FF_PROTOCOL, string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_BIT)) break;
|
||||
if(!protopirate_storage_copy_key(save_file, flipper_format, string_value)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, FF_FREQUENCY)) break;
|
||||
if(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, FF_PRESET, string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_string_if_present(
|
||||
save_file, flipper_format, "Custom_preset_module", string_value))
|
||||
break;
|
||||
if(!protopirate_storage_copy_hex_array_if_present(
|
||||
save_file, flipper_format, "Custom_preset_data", 1024))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_fields(
|
||||
save_file,
|
||||
flipper_format,
|
||||
protopirate_storage_base_u32_fields,
|
||||
COUNT_OF(protopirate_storage_base_u32_fields)))
|
||||
break;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key2", 8, NULL)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "KeyIdx")) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Seed")) break;
|
||||
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "ValidationField", 2))
|
||||
break;
|
||||
if(!protopirate_storage_copy_key_2(save_file, flipper_format, string_value)) break;
|
||||
if(!protopirate_storage_copy_hex_or_u32(save_file, flipper_format, "Key_3", 4)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Key_4")) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Fx")) break;
|
||||
if(!protopirate_storage_copy_hex_fixed(save_file, flipper_format, "Key1", 8, NULL)) break;
|
||||
if(!protopirate_storage_copy_u32_optional(save_file, flipper_format, "Check")) break;
|
||||
if(!protopirate_storage_copy_u32_array_if_present(
|
||||
save_file, flipper_format, "RAW_Data", 4096))
|
||||
break;
|
||||
if(!protopirate_storage_copy_u32_fields(
|
||||
save_file,
|
||||
flipper_format,
|
||||
protopirate_storage_tail_u32_fields,
|
||||
COUNT_OF(protopirate_storage_tail_u32_fields)))
|
||||
break;
|
||||
if(!protopirate_storage_copy_string_optional(
|
||||
save_file, flipper_format, FF_MANUFACTURE, string_value))
|
||||
break;
|
||||
status = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(string_value);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path) {
|
||||
furi_check(flipper_format);
|
||||
furi_check(full_path);
|
||||
|
||||
if(!protopirate_storage_init()) {
|
||||
FURI_LOG_E(TAG, "Failed to create app folder");
|
||||
return false;
|
||||
}
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* save_file = flipper_format_file_alloc(storage);
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
// Remove if it already exists (overwrite)
|
||||
if(storage_file_exists(storage, full_path)) {
|
||||
storage_simply_remove(storage, full_path);
|
||||
}
|
||||
|
||||
if(!flipper_format_file_open_new(save_file, full_path)) {
|
||||
FURI_LOG_E(TAG, "Failed to create file: %s", full_path);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write header");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Failed to write capture data");
|
||||
break;
|
||||
}
|
||||
|
||||
result = true;
|
||||
FURI_LOG_I(TAG, "Saved capture to %s", full_path);
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(save_file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
void protopirate_storage_delete_temp(void) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
if(storage_file_exists(storage, PROTOPIRATE_TEMP_FILE)) {
|
||||
storage_simply_remove(storage, PROTOPIRATE_TEMP_FILE);
|
||||
FURI_LOG_I(TAG, "Deleted temp file");
|
||||
}
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
bool protopirate_storage_save_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
FuriString* out_path) {
|
||||
furi_check(flipper_format);
|
||||
furi_check(protocol_name);
|
||||
furi_check(out_path);
|
||||
|
||||
if(!protopirate_storage_init()) {
|
||||
FURI_LOG_E(TAG, "Failed to create app folder");
|
||||
return false;
|
||||
}
|
||||
|
||||
FuriString* file_path = furi_string_alloc();
|
||||
|
||||
if(!protopirate_storage_get_next_filename(protocol_name, file_path)) {
|
||||
FURI_LOG_E(TAG, "Failed to get next filename");
|
||||
furi_string_free(file_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
FlipperFormat* save_file = flipper_format_file_alloc(storage);
|
||||
bool result = false;
|
||||
|
||||
do {
|
||||
if(!flipper_format_file_open_new(save_file, furi_string_get_cstr(file_path))) {
|
||||
FURI_LOG_E(TAG, "Failed to create file");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_header_cstr(save_file, "Flipper SubGhz Key File", 1)) {
|
||||
FURI_LOG_E(TAG, "Failed to write header");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!protopirate_storage_write_capture_data(save_file, flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Failed to write capture data");
|
||||
break;
|
||||
}
|
||||
|
||||
if(out_path) furi_string_set(out_path, file_path);
|
||||
|
||||
result = true;
|
||||
FURI_LOG_I(TAG, "Saved capture to %s", furi_string_get_cstr(file_path));
|
||||
|
||||
} while(false);
|
||||
|
||||
flipper_format_free(save_file);
|
||||
furi_string_free(file_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool protopirate_storage_delete_file(const char* file_path) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
bool result = storage_simply_remove(storage, file_path);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
|
||||
FURI_LOG_I(TAG, "Delete file %s: %s", file_path, result ? "OK" : "FAILED");
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// helpers/protopirate_storage.h
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define PROTOPIRATE_APP_FOLDER APP_DATA_PATH("saved")
|
||||
#define PROTOPIRATE_APP_EXTENSION ".psf"
|
||||
#define PROTOPIRATE_APP_FILE_VERSION 1
|
||||
#define PROTOPIRATE_TEMP_FILE APP_DATA_PATH("saved/.temp.psf")
|
||||
#define PROTOPIRATE_CACHE_FOLDER APP_DATA_PATH("cache")
|
||||
#define PROTOPIRATE_HISTORY_FOLDER APP_DATA_PATH("cache/history")
|
||||
|
||||
// Initialize storage (create folder if needed)
|
||||
bool protopirate_storage_init(void);
|
||||
|
||||
// Save a capture to a new file (auto-generated name)
|
||||
bool protopirate_storage_save_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
FuriString* out_path);
|
||||
|
||||
// Save a capture to a specific file path (user-chosen name)
|
||||
bool protopirate_storage_save_capture_to_path(FlipperFormat* flipper_format, const char* full_path);
|
||||
|
||||
// Save to temp file for emulation
|
||||
bool protopirate_storage_save_temp(FlipperFormat* flipper_format);
|
||||
|
||||
// Delete temp file
|
||||
void protopirate_storage_delete_temp(void);
|
||||
|
||||
// Get next available filename for a protocol
|
||||
bool protopirate_storage_get_next_filename(const char* protocol_name, FuriString* out_filename);
|
||||
|
||||
// Delete a file
|
||||
bool protopirate_storage_delete_file(const char* file_path);
|
||||
|
||||
// Load a file (caller must close with protopirate_storage_close_file)
|
||||
FlipperFormat* protopirate_storage_load_file(const char* file_path);
|
||||
|
||||
// Close a loaded file (by protopirate_storage_load_file only)
|
||||
void protopirate_storage_close_file(FlipperFormat* flipper_format);
|
||||
|
||||
// Check if file exists
|
||||
bool protopirate_storage_file_exists(const char* file_path);
|
||||
|
||||
bool protopirate_storage_ensure_history_folder(void);
|
||||
|
||||
void protopirate_storage_purge_temp_history_at_startup(void);
|
||||
|
||||
void protopirate_storage_wipe_history_cache(void);
|
||||
|
||||
bool protopirate_storage_save_history_capture(
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t seq,
|
||||
FuriString* out_path);
|
||||
|
||||
void protopirate_storage_build_history_path(uint32_t seq, FuriString* out);
|
||||
@@ -0,0 +1,78 @@
|
||||
// helpers/protopirate_types.h
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateViewVariableItemList,
|
||||
ProtoPirateViewSubmenu,
|
||||
ProtoPirateViewWidget,
|
||||
ProtoPirateViewReceiver,
|
||||
ProtoPirateViewAbout,
|
||||
ProtoPirateViewFileBrowser,
|
||||
ProtoPirateViewTextInput,
|
||||
} ProtoPirateView;
|
||||
|
||||
typedef enum {
|
||||
// Custom events for views
|
||||
ProtoPirateCustomEventViewReceiverOK,
|
||||
ProtoPirateCustomEventViewReceiverConfig,
|
||||
ProtoPirateCustomEventViewReceiverBack,
|
||||
ProtoPirateCustomEventViewReceiverDeleteItem,
|
||||
ProtoPirateCustomEventViewReceiverUnlock,
|
||||
// Custom events for scenes
|
||||
ProtoPirateCustomEventSceneReceiverUpdate,
|
||||
ProtoPirateCustomEventReceiverDeferredRxStart,
|
||||
ProtoPirateCustomEventSceneSettingLock,
|
||||
// File management
|
||||
ProtoPirateCustomEventReceiverInfoSave,
|
||||
ProtoPirateCustomEventReceiverInfoSaveConfirm,
|
||||
ProtoPirateCustomEventReceiverInfoEmulate,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceStart,
|
||||
ProtoPirateCustomEventReceiverInfoBruteforceCancel,
|
||||
ProtoPirateCustomEventSavedInfoDelete,
|
||||
// Emulator
|
||||
ProtoPirateCustomEventSavedInfoEmulate,
|
||||
ProtoPirateCustomEventEmulateTransmit,
|
||||
ProtoPirateCustomEventEmulateStop,
|
||||
ProtoPirateCustomEventEmulateExit,
|
||||
// Sub decode
|
||||
ProtoPirateCustomEventSubDecodeUpdate,
|
||||
ProtoPirateCustomEventSubDecodeSave,
|
||||
ProtoPirateCustomEventSubDecodeBruteforceStart,
|
||||
ProtoPirateCustomEventPsaBruteforceComplete,
|
||||
// File Browser
|
||||
ProtoPirateCustomEventSavedFileSelected,
|
||||
// Need saving confirmation
|
||||
ProtoPirateCustomEventSceneStay,
|
||||
ProtoPirateCustomEventSceneExit,
|
||||
// About scene
|
||||
ProtoPirateCustomEventAboutToggleEmulate,
|
||||
} ProtoPirateCustomEvent;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateLockOff,
|
||||
ProtoPirateLockOn,
|
||||
} ProtoPirateLock;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateTxRxStateIDLE,
|
||||
ProtoPirateTxRxStateRx,
|
||||
ProtoPirateTxRxStateTx,
|
||||
ProtoPirateTxRxStateSleep,
|
||||
} ProtoPirateTxRxState;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateHopperStateOFF,
|
||||
ProtoPirateHopperStateRunning,
|
||||
ProtoPirateHopperStatePause,
|
||||
ProtoPirateHopperStateRSSITimeOut,
|
||||
} ProtoPirateHopperState;
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateRxKeyStateIDLE,
|
||||
ProtoPirateRxKeyStateBack,
|
||||
ProtoPirateRxKeyStateStart,
|
||||
ProtoPirateRxKeyStateAddKey,
|
||||
} ProtoPirateRxKeyState;
|
||||
@@ -0,0 +1,142 @@
|
||||
// helpers/radio_device_loader.c
|
||||
#include "radio_device_loader.h"
|
||||
|
||||
#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
|
||||
#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include "../defines.h"
|
||||
|
||||
#define TAG "RadioDeviceLoader"
|
||||
|
||||
static bool radio_device_loader_otg_enabled_by_loader = false;
|
||||
|
||||
static void radio_device_loader_power_on() {
|
||||
uint8_t attempts = 0;
|
||||
while(!furi_hal_power_is_otg_enabled() && attempts++ < 5) {
|
||||
furi_hal_power_enable_otg();
|
||||
// CC1101 power-up time
|
||||
furi_delay_ms(10);
|
||||
}
|
||||
if(furi_hal_power_is_otg_enabled()) {
|
||||
radio_device_loader_otg_enabled_by_loader = true;
|
||||
}
|
||||
FURI_LOG_D(TAG, "OTG power enabled after %d attempts", attempts);
|
||||
}
|
||||
|
||||
static void radio_device_loader_power_off() {
|
||||
if(radio_device_loader_otg_enabled_by_loader && furi_hal_power_is_otg_enabled()) {
|
||||
furi_hal_power_disable_otg();
|
||||
radio_device_loader_otg_enabled_by_loader = false;
|
||||
FURI_LOG_D(TAG, "OTG power disabled");
|
||||
}
|
||||
}
|
||||
|
||||
bool radio_device_loader_is_connect_external(const char* name) {
|
||||
bool is_connect = false;
|
||||
bool is_otg_enabled = furi_hal_power_is_otg_enabled();
|
||||
|
||||
if(!is_otg_enabled) {
|
||||
radio_device_loader_power_on();
|
||||
}
|
||||
|
||||
const SubGhzDevice* device = subghz_devices_get_by_name(name);
|
||||
if(device) {
|
||||
is_connect = subghz_devices_is_connect(device);
|
||||
FURI_LOG_D(TAG, "External device '%s' connect check: %s", name, is_connect ? "YES" : "NO");
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "Could not get device by name: %s", name);
|
||||
}
|
||||
|
||||
if(!is_otg_enabled) {
|
||||
radio_device_loader_power_off();
|
||||
}
|
||||
return is_connect;
|
||||
}
|
||||
|
||||
const SubGhzDevice* radio_device_loader_set(
|
||||
const SubGhzDevice* current_radio_device,
|
||||
SubGhzRadioDeviceType radio_device_type) {
|
||||
const SubGhzDevice* target_radio_device = NULL;
|
||||
|
||||
// Decide the target device first (external if requested+present, else internal)
|
||||
if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
|
||||
radio_device_loader_is_connect_external(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
|
||||
radio_device_loader_power_on();
|
||||
target_radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
|
||||
if(!target_radio_device) {
|
||||
FURI_LOG_E(TAG, "Failed to get external CC1101 device, falling back to internal");
|
||||
}
|
||||
}
|
||||
|
||||
if(!target_radio_device) {
|
||||
target_radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
|
||||
if(!target_radio_device) {
|
||||
FURI_LOG_E(TAG, "Failed to get internal CC1101 device");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// If we’re already on the target device, don’t reload
|
||||
if(current_radio_device == target_radio_device) {
|
||||
if(target_radio_device == subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
|
||||
FURI_LOG_I(TAG, "External CC1101 already selected");
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Internal CC1101 already selected");
|
||||
}
|
||||
return target_radio_device;
|
||||
}
|
||||
|
||||
// Cleanly stop the current device before switching
|
||||
if(current_radio_device) {
|
||||
radio_device_loader_end(current_radio_device);
|
||||
}
|
||||
|
||||
// Start the target device
|
||||
subghz_devices_begin(target_radio_device);
|
||||
|
||||
// Log what we ended up with
|
||||
if(target_radio_device == subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
|
||||
FURI_LOG_I(TAG, "Switched to external CC1101");
|
||||
} else {
|
||||
if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101) {
|
||||
FURI_LOG_I(TAG, "External requested but unavailable; switched to internal CC1101");
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Switched to internal CC1101");
|
||||
}
|
||||
}
|
||||
|
||||
return target_radio_device;
|
||||
}
|
||||
|
||||
bool radio_device_loader_is_external(const SubGhzDevice* radio_device) {
|
||||
if(!radio_device) {
|
||||
FURI_LOG_W(TAG, "is_external called with NULL device");
|
||||
return false;
|
||||
}
|
||||
|
||||
const SubGhzDevice* internal_device =
|
||||
subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
|
||||
bool is_external = (radio_device != internal_device);
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"is_external check: device=%p, internal=%p, result=%s",
|
||||
radio_device,
|
||||
internal_device,
|
||||
is_external ? "EXTERNAL" : "INTERNAL");
|
||||
|
||||
return is_external;
|
||||
}
|
||||
|
||||
void radio_device_loader_end(const SubGhzDevice* radio_device) {
|
||||
furi_check(radio_device);
|
||||
|
||||
if(radio_device != subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME)) {
|
||||
subghz_devices_end(radio_device);
|
||||
FURI_LOG_I(TAG, "External radio device ended");
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "Internal radio device - no cleanup needed");
|
||||
}
|
||||
radio_device_loader_power_off();
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// helpers/radio_device_loader.h
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/devices/devices.h>
|
||||
|
||||
#define SUBGHZ_DEVICE_CC1101_INT_NAME "cc1101_int"
|
||||
#define SUBGHZ_DEVICE_CC1101_EXT_NAME "cc1101_ext"
|
||||
|
||||
/** SubGhzRadioDeviceType */
|
||||
typedef enum {
|
||||
SubGhzRadioDeviceTypeInternal,
|
||||
SubGhzRadioDeviceTypeExternalCC1101,
|
||||
} SubGhzRadioDeviceType;
|
||||
|
||||
const SubGhzDevice* radio_device_loader_set(
|
||||
const SubGhzDevice* current_radio_device,
|
||||
SubGhzRadioDeviceType radio_device_type);
|
||||
|
||||
bool radio_device_loader_is_connect_external(const char* name);
|
||||
bool radio_device_loader_is_external(const SubGhzDevice* radio_device);
|
||||
void radio_device_loader_end(const SubGhzDevice* radio_device);
|
||||
@@ -0,0 +1,376 @@
|
||||
#include "raw_file_reader.h"
|
||||
|
||||
#ifdef ENABLE_SUB_DECODE_SCENE
|
||||
#include <stdint.h>
|
||||
#include <toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "../protocols/protocols_common.h"
|
||||
|
||||
#define TAG "RawFileReader"
|
||||
|
||||
static const char local_flipper_format_delimiter = ':';
|
||||
static const char local_flipper_format_comment = '#';
|
||||
static const char local_flipper_format_eoln = '\n';
|
||||
static const char local_flipper_format_eolr = '\r';
|
||||
|
||||
struct FlipperFormat {
|
||||
Stream* stream;
|
||||
bool strict_mode;
|
||||
};
|
||||
|
||||
RawFileReader* raw_file_reader_alloc(void) {
|
||||
RawFileReader* reader = malloc(sizeof(RawFileReader));
|
||||
furi_check(reader);
|
||||
memset(reader, 0, sizeof(RawFileReader));
|
||||
return reader;
|
||||
}
|
||||
|
||||
void raw_file_reader_free(RawFileReader* reader) {
|
||||
if(!reader) return;
|
||||
raw_file_reader_close(reader);
|
||||
free(reader);
|
||||
}
|
||||
|
||||
static inline bool local_flipper_format_stream_is_space(char c) {
|
||||
return c == ' ' || c == '\t' || c == local_flipper_format_eolr;
|
||||
}
|
||||
|
||||
static bool local_flipper_format_stream_read_value(Stream* stream, FuriString* value, bool* last) {
|
||||
enum {
|
||||
LeadingSpace,
|
||||
ReadValue,
|
||||
TrailingSpace
|
||||
} state = LeadingSpace;
|
||||
const size_t buffer_size = 32;
|
||||
uint8_t buffer[buffer_size];
|
||||
bool result = false;
|
||||
bool error = false;
|
||||
|
||||
furi_string_reset(value);
|
||||
|
||||
while(true) {
|
||||
size_t was_read = stream_read(stream, buffer, buffer_size);
|
||||
|
||||
if(was_read == 0) {
|
||||
if(state != LeadingSpace && stream_eof(stream)) {
|
||||
result = true;
|
||||
*last = true;
|
||||
} else {
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < was_read; i++) {
|
||||
const uint8_t data = buffer[i];
|
||||
|
||||
if(state == LeadingSpace) {
|
||||
if(local_flipper_format_stream_is_space(data)) {
|
||||
continue;
|
||||
} else if(data == local_flipper_format_eoln) {
|
||||
stream_seek(stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent);
|
||||
error = true;
|
||||
break;
|
||||
} else {
|
||||
state = ReadValue;
|
||||
furi_string_push_back(value, data);
|
||||
}
|
||||
} else if(state == ReadValue) {
|
||||
if(local_flipper_format_stream_is_space(data)) {
|
||||
state = TrailingSpace;
|
||||
} else if(data == local_flipper_format_eoln) {
|
||||
if(!stream_seek(
|
||||
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
} else {
|
||||
result = true;
|
||||
*last = true;
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
furi_string_push_back(value, data);
|
||||
}
|
||||
} else if(state == TrailingSpace) {
|
||||
if(local_flipper_format_stream_is_space(data)) {
|
||||
continue;
|
||||
} else if(!stream_seek(
|
||||
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
} else {
|
||||
*last = (data == local_flipper_format_eoln);
|
||||
result = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(error || result) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool local_flipper_format_stream_read_valid_key(Stream* stream, FuriString* key) {
|
||||
furi_string_reset(key);
|
||||
const size_t buffer_size = 32;
|
||||
uint8_t buffer[buffer_size];
|
||||
|
||||
bool found = false;
|
||||
bool error = false;
|
||||
bool accumulate = true;
|
||||
bool new_line = true;
|
||||
|
||||
while(true) {
|
||||
size_t was_read = stream_read(stream, buffer, buffer_size);
|
||||
if(was_read == 0) break;
|
||||
|
||||
for(size_t i = 0; i < was_read; i++) {
|
||||
uint8_t data = buffer[i];
|
||||
if(data == local_flipper_format_eoln) {
|
||||
// EOL found, clean data, start accumulating data and set the new_line flag
|
||||
furi_string_reset(key);
|
||||
accumulate = true;
|
||||
new_line = true;
|
||||
} else if(data == local_flipper_format_eolr) {
|
||||
// ignore
|
||||
} else if(data == local_flipper_format_comment && new_line) {
|
||||
// if there is a comment character and we are at the beginning of a new line
|
||||
// do not accumulate comment data and reset the new_line flag
|
||||
accumulate = false;
|
||||
new_line = false;
|
||||
} else if(data == local_flipper_format_delimiter) {
|
||||
if(new_line) {
|
||||
// we are on a "new line" and found the delimiter
|
||||
// this can only be if we have previously found some kind of key, so
|
||||
// clear the data, set the flag that we no longer want to accumulate data
|
||||
// and reset the new_line flag
|
||||
furi_string_reset(key);
|
||||
accumulate = false;
|
||||
new_line = false;
|
||||
} else {
|
||||
// parse the delimiter only if we are accumulating data
|
||||
if(accumulate) {
|
||||
// we found the delimiter, move the rw pointer to the delimiter location
|
||||
// and signal that we have found something
|
||||
if(!stream_seek(
|
||||
stream, (int32_t)i - (int32_t)was_read, StreamOffsetFromCurrent)) {
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// just new symbol, reset the new_line flag
|
||||
new_line = false;
|
||||
if(accumulate) {
|
||||
// and accumulate data if we want
|
||||
furi_string_push_back(key, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(found || error) break;
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool
|
||||
local_flipper_format_stream_seek_to_key(Stream* stream, const char* key, bool strict_mode) {
|
||||
bool found = false;
|
||||
FuriString* read_key;
|
||||
|
||||
read_key = furi_string_alloc();
|
||||
|
||||
while(!stream_eof(stream)) {
|
||||
if(local_flipper_format_stream_read_valid_key(stream, read_key)) {
|
||||
if(furi_string_cmp_str(read_key, key) == 0) {
|
||||
if(!stream_seek(stream, 2, StreamOffsetFromCurrent)) break;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
} else if(strict_mode) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
furi_string_free(read_key);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool local_flipper_format_stream_get_value_count(
|
||||
Stream* stream,
|
||||
const char* key,
|
||||
uint32_t* count,
|
||||
bool strict_mode) {
|
||||
bool result = false;
|
||||
bool last = false;
|
||||
|
||||
FuriString* value;
|
||||
value = furi_string_alloc();
|
||||
|
||||
do {
|
||||
if(!local_flipper_format_stream_seek_to_key(stream, key, strict_mode)) break;
|
||||
*count = 0;
|
||||
|
||||
result = true;
|
||||
while(true) {
|
||||
if(!local_flipper_format_stream_read_value(stream, value, &last)) {
|
||||
result = false;
|
||||
break;
|
||||
}
|
||||
|
||||
*count = *count + 1;
|
||||
if(last) break;
|
||||
}
|
||||
|
||||
} while(false);
|
||||
|
||||
furi_string_free(value);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool raw_file_reader_open(RawFileReader* reader, const char* file_path) {
|
||||
if(!reader || !file_path) return false;
|
||||
|
||||
raw_file_reader_close(reader);
|
||||
|
||||
reader->storage = furi_record_open(RECORD_STORAGE);
|
||||
reader->storage_opened = true;
|
||||
reader->ff = flipper_format_file_alloc(reader->storage);
|
||||
|
||||
if(!flipper_format_file_open_existing(reader->ff, file_path)) {
|
||||
FURI_LOG_E(TAG, "Failed to open file: %s", file_path);
|
||||
raw_file_reader_close(reader);
|
||||
return false;
|
||||
}
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
uint32_t version = 0;
|
||||
|
||||
bool valid = false;
|
||||
do {
|
||||
if(!flipper_format_read_header(reader->ff, temp_str, &version)) {
|
||||
FURI_LOG_E(TAG, "Failed to read header");
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(temp_str, "Flipper SubGhz RAW File") != 0) {
|
||||
FURI_LOG_E(TAG, "Not a RAW file");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(reader->ff, FF_PROTOCOL, temp_str)) {
|
||||
FURI_LOG_E(TAG, "Missing Protocol field");
|
||||
break;
|
||||
}
|
||||
|
||||
if(furi_string_cmp_str(temp_str, "RAW") != 0) {
|
||||
FURI_LOG_E(TAG, "Protocol is not RAW");
|
||||
break;
|
||||
}
|
||||
|
||||
valid = true;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
if(!valid) {
|
||||
raw_file_reader_close(reader);
|
||||
return false;
|
||||
}
|
||||
|
||||
reader->buffer_count = 0;
|
||||
reader->buffer_index = 0;
|
||||
reader->file_finished = false;
|
||||
reader->current_level = true;
|
||||
|
||||
FURI_LOG_I(TAG, "Opened RAW file: %s", file_path);
|
||||
|
||||
reader->count = 0;
|
||||
uint32_t temp_count = 0;
|
||||
|
||||
while(local_flipper_format_stream_get_value_count(
|
||||
reader->ff->stream, "RAW_Data", &temp_count, reader->ff->strict_mode)) {
|
||||
//reader->file_finished = true;
|
||||
reader->count += temp_count;
|
||||
}
|
||||
flipper_format_rewind(reader->ff);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void raw_file_reader_close(RawFileReader* reader) {
|
||||
if(!reader) return;
|
||||
|
||||
if(reader->ff) {
|
||||
flipper_format_free(reader->ff);
|
||||
reader->ff = NULL;
|
||||
}
|
||||
|
||||
if(reader->storage_opened) {
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
reader->storage_opened = false;
|
||||
}
|
||||
|
||||
reader->storage = NULL;
|
||||
reader->buffer_count = 0;
|
||||
reader->buffer_index = 0;
|
||||
reader->count = 0;
|
||||
reader->file_finished = false;
|
||||
}
|
||||
|
||||
static bool raw_file_reader_load_chunk(RawFileReader* reader) {
|
||||
if(reader->file_finished) return false;
|
||||
|
||||
size_t to_read = (reader->count < RAW_READER_BUFFER_SIZE) ? reader->count :
|
||||
RAW_READER_BUFFER_SIZE;
|
||||
|
||||
if(!flipper_format_read_int32(reader->ff, "RAW_Data", reader->buffer, to_read)) {
|
||||
reader->file_finished = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
reader->buffer_count = to_read;
|
||||
reader->buffer_index = 0;
|
||||
reader->count -= to_read;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration) {
|
||||
if(!reader || !level || !duration) return false;
|
||||
|
||||
if(memmgr_get_free_heap() < 1024) {
|
||||
FURI_LOG_E(TAG, "Not enough memory to continue reading");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(reader->buffer_index >= reader->buffer_count) {
|
||||
if(!raw_file_reader_load_chunk(reader)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t value = reader->buffer[reader->buffer_index++];
|
||||
|
||||
if(value >= 0) {
|
||||
*level = true;
|
||||
*duration = (uint32_t)value;
|
||||
} else {
|
||||
*level = false;
|
||||
*duration = (uint32_t)(-value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool raw_file_reader_is_finished(RawFileReader* reader) {
|
||||
if(!reader) return true;
|
||||
return reader->file_finished && (reader->buffer_index >= reader->buffer_count);
|
||||
}
|
||||
#endif // ENABLE_SUB_DECODE_SCENE
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "../protopirate_app_i.h"
|
||||
#ifdef ENABLE_SUB_DECODE_SCENE
|
||||
#include <furi.h>
|
||||
#include <storage/storage.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define RAW_READER_BUFFER_SIZE 512
|
||||
|
||||
typedef struct {
|
||||
Storage* storage;
|
||||
FlipperFormat* ff;
|
||||
int32_t buffer[RAW_READER_BUFFER_SIZE];
|
||||
size_t buffer_count;
|
||||
size_t buffer_index;
|
||||
uint32_t count;
|
||||
bool file_finished;
|
||||
bool current_level;
|
||||
bool storage_opened;
|
||||
} RawFileReader;
|
||||
|
||||
RawFileReader* raw_file_reader_alloc(void);
|
||||
void raw_file_reader_free(RawFileReader* reader);
|
||||
bool raw_file_reader_open(RawFileReader* reader, const char* file_path);
|
||||
void raw_file_reader_close(RawFileReader* reader);
|
||||
bool raw_file_reader_get_next(RawFileReader* reader, bool* level, uint32_t* duration);
|
||||
bool raw_file_reader_is_finished(RawFileReader* reader);
|
||||
#endif // ENABLE_SUB_DECODE_SCENE
|
||||
|
After Width: | Height: | Size: 448 B |
|
After Width: | Height: | Size: 385 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 994 B |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 95 B |
@@ -0,0 +1,32 @@
|
||||
Filetype: Flipper SubGhz Keystore File
|
||||
Version: 0
|
||||
Encryption: 1
|
||||
IV: 42 69 67 42 72 6F 74 68 72 57 61 74 63 68 65 73
|
||||
DDCCB3575BB076A57F94B6E31CB6FF6DE4AD7D020EBF225BB046D0AA202748FC
|
||||
B9AC4402F1119004DB4857C9986B97CAE8834B6356A01E405796004BE61FD583
|
||||
B68059DADBD99A913BE87005120503710728E0D85BD1BBCC5AB92DB86C5C8A70
|
||||
1B47835E16262394EA1A1AE54A5AB9CF05BFFFD6D4FB35C0F68EAF8D9F1256AC
|
||||
8F0D12248CD35A63A625E798D9743618F723338159B620C5960F10BBC28A7194
|
||||
4D638F8F7B87FAFA150731D67CC59C1D1C77D62991A6735054E63F0580B83BEA
|
||||
F19B69682FB1D65628074C1ED817BAC35B51A1A4977201B65BFEBBE14F436BB46E6108D6320BF4F67D14285A15119B5F
|
||||
0E5400DE76B4B737D9E86561328DD5FC06F5C7798666640EE143B05D228754CE
|
||||
4C5A993BCD310E4EE11E9AB0E092889798D48FA12F7633E254578FD88859E67C
|
||||
074FC9A36207CA20851708680C7AE7D680B1DE16DAF79C5A4502E5EF4F8A103E
|
||||
65F94C6C6FD191A7BB5FCFA77940145411C7588495417DEDD1B0B6CE771EE35F
|
||||
DE08B464B312A3190246EC46CE17A41874C07E14E652213E67FC028CF8DA2064
|
||||
0117D991241760F0E36136F4DB676DC1C87981DD201A66DC9031CD3543160E0824FF61B9126CDE7C580D999BD1BEF0BA
|
||||
3C6D9C324DB570278E7D67F73C01B2782BC2430C483933F7D90B7F543215A63B7A5064A1F5A27A240ABDFFA41F3099AA
|
||||
2A77CFCAEC76BBAF33AF9A84097351C2DD17FAE884CB1EDE762E3F130AB55614
|
||||
F6299EC2AF12AC0619AD8E2EBA39D92401D0C1C7045B0C0629DCD030FFDFFCEC
|
||||
1EB9B7D48E72A43A81BFD774943384C3DAAA99193F06C024E1480C0E72018E45
|
||||
16880430929593B97C8F5FEB23455550685ECC9CC2A6E43A1ED23ECF1FA8B95E
|
||||
36F6FD37E1728BF8F7411181F39C063942F4200341AEF70D8C7F5E75F9F4867CC474BB2B0A373D86859DA7FBD0576AEA
|
||||
84B20904AA979685846EA784C89C5AE3AAF1E9669B8B4D081061D863EFDB6C47
|
||||
3932EBA44E1A3B1C53AF210DAD5B78DB4EB6BEC9D138B6C1DCB6A681AAE5B383
|
||||
D94A3E8C9F9D74337660D49F3A39B0AFFB6F630EF879548A041FF0731558DC4F86FEDAEBD6AFC537A5C5307E450B9846
|
||||
54DC1CE43222E743F72C168AC66A818E5D4305A63C90602DC67095A41A6A4B5FA2F484BCA174A686CFCD256AA88A9213
|
||||
A598EE96C92DA9EF21406E1705D4C6527D7651FB6C9AB0750615937C2AECDD5B
|
||||
38AA52A3EC8EC608C990D13F2FE15977C7926A355DCE89509429AD2347123CB5
|
||||
E6D2647DBF847DB721263B5BD2DD355F6970228E19AC0B69AE8A669C6E2F14E7
|
||||
423BB75987A636BD8696CA11E511BCD99CFC7D8E557FD18E04DCF52DD51B2E4C
|
||||
F2BA90DB6F8298F92C88B5F7FB274B8650FE84C3E9542AB8B9744F2FD9BA277B9FBB9B17026FF3BF364AD625F28FDE66
|
||||
@@ -0,0 +1,6 @@
|
||||
Filetype: Flipper SubGhz Keystore RAW File
|
||||
Version: 0
|
||||
Encryption: 1
|
||||
IV: B9 A8 C0 F6 9A 78 B1 CB 3E A7 69 0E 86 53 9F A7
|
||||
Encrypt_data: RAW
|
||||
BA2A8B783B6DB3E17DEF07EA2AE0104FAF722A07775E96338D734FFD0EA8BB29BF1A5A6451B1784AE2B51D967EFB45EAFAE2C20D5721553727C026D4009BBCB0
|
||||
@@ -0,0 +1,454 @@
|
||||
#include "aut64.h"
|
||||
#include <string.h>
|
||||
|
||||
// https://www.usenix.org/system/files/conference/usenixsecurity16/sec16_paper_garcia.pdf
|
||||
|
||||
/*
|
||||
* AUT64 algorithm, 12 rounds
|
||||
* 8 bytes block size, 8 bytes key size
|
||||
* 8 bytes pbox size, 16 bytes sbox size
|
||||
*
|
||||
* Based on: Reference AUT64 implementation in JavaScript (aut64.js)
|
||||
* Vencislav Atanasov, 2025-09-13
|
||||
*
|
||||
* Based on: Reference AUT64 implementation in python
|
||||
* C Hicks, hkscy.org, 03-01-19
|
||||
*/
|
||||
|
||||
static const uint8_t table_ln[AUT64_NUM_ROUNDS][AUT64_BLOCK_SIZE] = {
|
||||
{0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 0
|
||||
{0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 1
|
||||
{0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 2
|
||||
{0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 3
|
||||
{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 4
|
||||
{0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 5
|
||||
{0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 6
|
||||
{0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 7
|
||||
{0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 8
|
||||
{0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 9
|
||||
{0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 10
|
||||
{0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 11
|
||||
};
|
||||
|
||||
static const uint8_t table_un[AUT64_NUM_ROUNDS][AUT64_BLOCK_SIZE] = {
|
||||
{0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 0
|
||||
{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 1
|
||||
{0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 2
|
||||
{0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 3
|
||||
{0x5, 0x4, 0x7, 0x6, 0x1, 0x0, 0x3, 0x2}, // Round 4
|
||||
{0x4, 0x5, 0x6, 0x7, 0x0, 0x1, 0x2, 0x3}, // Round 5
|
||||
{0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0x0}, // Round 6
|
||||
{0x6, 0x7, 0x4, 0x5, 0x2, 0x3, 0x0, 0x1}, // Round 7
|
||||
{0x3, 0x2, 0x1, 0x0, 0x7, 0x6, 0x5, 0x4}, // Round 8
|
||||
{0x2, 0x3, 0x0, 0x1, 0x6, 0x7, 0x4, 0x5}, // Round 9
|
||||
{0x1, 0x0, 0x3, 0x2, 0x5, 0x4, 0x7, 0x6}, // Round 10
|
||||
{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, // Round 11
|
||||
};
|
||||
|
||||
static const uint8_t table_offset[AUT64_OFFSET_TABLE_SIZE] = {
|
||||
// 0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // 0
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, // 1
|
||||
0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x3, 0x1, 0x7, 0x5, 0xB, 0x9, 0xF, 0xD, // 2
|
||||
0x0, 0x3, 0x6, 0x5, 0xC, 0xF, 0xA, 0x9, 0xB, 0x8, 0xD, 0xE, 0x7, 0x4, 0x1, 0x2, // 3
|
||||
0x0, 0x4, 0x8, 0xC, 0x3, 0x7, 0xB, 0xF, 0x6, 0x2, 0xE, 0xA, 0x5, 0x1, 0xD, 0x9, // 4
|
||||
0x0, 0x5, 0xA, 0xF, 0x7, 0x2, 0xD, 0x8, 0xE, 0xB, 0x4, 0x1, 0x9, 0xC, 0x3, 0x6, // 5
|
||||
0x0, 0x6, 0xC, 0xA, 0xB, 0xD, 0x7, 0x1, 0x5, 0x3, 0x9, 0xF, 0xE, 0x8, 0x2, 0x4, // 6
|
||||
0x0, 0x7, 0xE, 0x9, 0xF, 0x8, 0x1, 0x6, 0xD, 0xA, 0x3, 0x4, 0x2, 0x5, 0xC, 0xB, // 7
|
||||
0x0, 0x8, 0x3, 0xB, 0x6, 0xE, 0x5, 0xD, 0xC, 0x4, 0xF, 0x7, 0xA, 0x2, 0x9, 0x1, // 8
|
||||
0x0, 0x9, 0x1, 0x8, 0x2, 0xB, 0x3, 0xA, 0x4, 0xD, 0x5, 0xC, 0x6, 0xF, 0x7, 0xE, // 9
|
||||
0x0, 0xA, 0x7, 0xD, 0xE, 0x4, 0x9, 0x3, 0xF, 0x5, 0x8, 0x2, 0x1, 0xB, 0x6, 0xC, // A
|
||||
0x0, 0xB, 0x5, 0xE, 0xA, 0x1, 0xF, 0x4, 0x7, 0xC, 0x2, 0x9, 0xD, 0x6, 0x8, 0x3, // B
|
||||
0x0, 0xC, 0xB, 0x7, 0x5, 0x9, 0xE, 0x2, 0xA, 0x6, 0x1, 0xD, 0xF, 0x3, 0x4, 0x8, // C
|
||||
0x0, 0xD, 0x9, 0x4, 0x1, 0xC, 0x8, 0x5, 0x2, 0xF, 0xB, 0x6, 0x3, 0xE, 0xA, 0x7, // D
|
||||
0x0, 0xE, 0xF, 0x1, 0xD, 0x3, 0x2, 0xC, 0x9, 0x7, 0x6, 0x8, 0x4, 0xA, 0xB, 0x5, // E
|
||||
0x0, 0xF, 0xD, 0x2, 0x9, 0x6, 0x4, 0xB, 0x1, 0xE, 0xC, 0x3, 0x8, 0x7, 0x5, 0xA // F
|
||||
};
|
||||
|
||||
static const uint8_t table_sub[AUT64_SBOX_SIZE] = {
|
||||
0x0,
|
||||
0x1,
|
||||
0x9,
|
||||
0xE,
|
||||
0xD,
|
||||
0xB,
|
||||
0x7,
|
||||
0x6,
|
||||
0xF,
|
||||
0x2,
|
||||
0xC,
|
||||
0x5,
|
||||
0xA,
|
||||
0x4,
|
||||
0x3,
|
||||
0x8,
|
||||
};
|
||||
|
||||
// Build an inverse/permutation table.
|
||||
// Sentinel 0xFF is used to detect missing entries and duplicates.
|
||||
// Returns AUT64_OK on success, otherwise AUT64_ERR_INVALID_KEY.
|
||||
static int reverse_box(uint8_t* reversed, const uint8_t* box, size_t len) {
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
reversed[i] = 0xFF;
|
||||
}
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
const uint8_t v = box[i];
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
if(v >= len) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
if(reversed[v] != 0xFF) {
|
||||
// Duplicate value means it is not a permutation.
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
#endif
|
||||
reversed[v] = (uint8_t)i;
|
||||
}
|
||||
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
for(i = 0; i < len; i++) {
|
||||
if(reversed[i] == 0xFF) {
|
||||
// Missing mapping.
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
|
||||
// Validate that 'box' is a permutation of 0..len-1.
|
||||
// Uses 0xFF sentinel logic to detect duplicates/missing values.
|
||||
static int validate_box_is_permutation(const uint8_t* box, size_t len) {
|
||||
uint8_t inv[32]; // enough for pbox (8) and sbox (16)
|
||||
size_t i;
|
||||
|
||||
if(len > sizeof(inv)) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
inv[i] = 0xFF;
|
||||
}
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
const uint8_t v = box[i];
|
||||
if(v >= len) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
if(inv[v] != 0xFF) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
inv[v] = (uint8_t)i;
|
||||
}
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
if(inv[i] == 0xFF) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
|
||||
// Validate that a key is structurally correct:
|
||||
// - key nibbles are in range 0..15
|
||||
// - pbox is a permutation of 0..7
|
||||
// - sbox is a permutation of 0..15
|
||||
// return AUT64_OK or AUT64_ERR_INVALID_KEY/AUT64_ERR_NULL_POINTER
|
||||
int aut64_validate_key(const struct aut64_key* key) {
|
||||
uint8_t i;
|
||||
int rc;
|
||||
|
||||
if(!key) {
|
||||
return AUT64_ERR_NULL_POINTER;
|
||||
}
|
||||
|
||||
// key->key[] is treated as nibbles in multiple places (table_sub indexing, offset building).
|
||||
for(i = 0; i < AUT64_KEY_SIZE; i++) {
|
||||
if(key->key[i] >= AUT64_SBOX_SIZE) {
|
||||
return AUT64_ERR_INVALID_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
rc = validate_box_is_permutation(key->pbox, AUT64_PBOX_SIZE);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = validate_box_is_permutation(key->sbox, AUT64_SBOX_SIZE);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
|
||||
#endif // AUT64_ENABLE_VALIDATIONS
|
||||
|
||||
// Compute one 4-bit contribution to the round key
|
||||
static uint8_t key_nibble(
|
||||
const struct aut64_key* key,
|
||||
uint8_t nibble,
|
||||
const uint8_t table[AUT64_BLOCK_SIZE],
|
||||
uint8_t iteration) {
|
||||
const uint8_t keyValue = key->key[table[iteration]];
|
||||
const uint8_t offset = (uint8_t)((keyValue << 4) | nibble);
|
||||
return table_offset[offset];
|
||||
}
|
||||
|
||||
// Compute the round compression byte derived from the current state and the key for a given round.
|
||||
static uint8_t round_key(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) {
|
||||
uint8_t result_hi = 0, result_lo = 0;
|
||||
|
||||
for(int i = 0; i < AUT64_BLOCK_SIZE - 1; i++) {
|
||||
result_hi ^= key_nibble(key, (uint8_t)(state[i] >> 4), table_un[roundN], (uint8_t)i);
|
||||
result_lo ^= key_nibble(key, (uint8_t)(state[i] & 0x0F), table_ln[roundN], (uint8_t)i);
|
||||
}
|
||||
|
||||
return (uint8_t)((result_hi << 4) | result_lo);
|
||||
}
|
||||
|
||||
// Compute the transformed key nibble used as an offset for final-byte processing in a round.
|
||||
static uint8_t
|
||||
final_byte_nibble(const struct aut64_key* key, const uint8_t table[AUT64_BLOCK_SIZE]) {
|
||||
const uint8_t keyValue = key->key[table[AUT64_BLOCK_SIZE - 1]];
|
||||
return (uint8_t)(table_sub[keyValue] << 4);
|
||||
}
|
||||
|
||||
// Compute the inverse lookup for a final-byte nibble during encryption.
|
||||
static uint8_t encrypt_final_byte_nibble(
|
||||
const struct aut64_key* key,
|
||||
uint8_t nibble,
|
||||
const uint8_t table[AUT64_BLOCK_SIZE]) {
|
||||
const uint8_t offset = final_byte_nibble(key, table);
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(table_offset[(uint8_t)(offset + i)] == nibble) {
|
||||
return (uint8_t)i;
|
||||
}
|
||||
}
|
||||
// Should never happen for valid inputs; return 0 as a defined value.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Perform the compression step for one encryption round, producing the new last byte.
|
||||
static uint8_t
|
||||
encrypt_compress(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) {
|
||||
const uint8_t roundKey = round_key(key, state, roundN);
|
||||
uint8_t result_hi = (uint8_t)(roundKey >> 4), result_lo = (uint8_t)(roundKey & 0x0F);
|
||||
|
||||
result_hi ^= encrypt_final_byte_nibble(
|
||||
key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] >> 4), table_un[roundN]);
|
||||
result_lo ^= encrypt_final_byte_nibble(
|
||||
key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] & 0x0F), table_ln[roundN]);
|
||||
|
||||
return (uint8_t)((result_hi << 4) | result_lo);
|
||||
}
|
||||
|
||||
// Reverse the final-byte nibble transformation during decryption.
|
||||
static uint8_t decrypt_final_byte_nibble(
|
||||
const struct aut64_key* key,
|
||||
uint8_t nibble,
|
||||
const uint8_t table[AUT64_BLOCK_SIZE],
|
||||
uint8_t result) {
|
||||
const uint8_t offset = final_byte_nibble(key, table);
|
||||
return table_offset[(uint8_t)((result ^ nibble) + offset)];
|
||||
}
|
||||
|
||||
// Perform the compression step for one decryption round, restoring the previous last byte.
|
||||
static uint8_t
|
||||
decrypt_compress(const struct aut64_key* key, const uint8_t* state, uint8_t roundN) {
|
||||
const uint8_t roundKey = round_key(key, state, roundN);
|
||||
uint8_t result_hi = (uint8_t)(roundKey >> 4), result_lo = (uint8_t)(roundKey & 0x0F);
|
||||
|
||||
result_hi = decrypt_final_byte_nibble(
|
||||
key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] >> 4), table_un[roundN], result_hi);
|
||||
result_lo = decrypt_final_byte_nibble(
|
||||
key, (uint8_t)(state[AUT64_BLOCK_SIZE - 1] & 0x0F), table_ln[roundN], result_lo);
|
||||
|
||||
return (uint8_t)((result_hi << 4) | result_lo);
|
||||
}
|
||||
|
||||
// Apply the S-box substitution to a single byte.
|
||||
static uint8_t substitute(const struct aut64_key* key, uint8_t byte) {
|
||||
return (uint8_t)((key->sbox[byte >> 4] << 4) | key->sbox[byte & 0x0F]);
|
||||
}
|
||||
|
||||
// Apply the byte-level permutation (pbox) to the 8-byte state block.
|
||||
static void permute_bytes(const struct aut64_key* key, uint8_t* state) {
|
||||
// Key is validated up-front, so pbox[] is a correct permutation of 0..7.
|
||||
uint8_t result[AUT64_PBOX_SIZE];
|
||||
|
||||
for(int i = 0; i < AUT64_PBOX_SIZE; i++) {
|
||||
result[key->pbox[i]] = state[i];
|
||||
}
|
||||
|
||||
memcpy(state, result, AUT64_PBOX_SIZE);
|
||||
}
|
||||
|
||||
// Apply bit-level permutation to a single byte using the pbox mapping.
|
||||
static uint8_t permute_bits(const struct aut64_key* key, uint8_t byte) {
|
||||
// Key is validated up-front, so pbox[] is a correct permutation of 0..7.
|
||||
uint8_t result = 0;
|
||||
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(byte & (1 << i)) {
|
||||
result |= (uint8_t)(1 << key->pbox[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Encrypt one 8-byte block in place using the provided validated key.
|
||||
int aut64_encrypt(const struct aut64_key* key, uint8_t* message) {
|
||||
int rc;
|
||||
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
if(!key || !message) {
|
||||
return AUT64_ERR_NULL_POINTER;
|
||||
}
|
||||
// Validate key before doing anything. This prevents silent, unsafe behavior.
|
||||
rc = aut64_validate_key(key);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Build a reversed key (inverse pbox and sbox) ...
|
||||
// Fully initialize to avoid any uninitialized fields/padding.
|
||||
struct aut64_key reverse_key = (struct aut64_key){0};
|
||||
reverse_key.index = key->index;
|
||||
memcpy(reverse_key.key, key->key, AUT64_KEY_SIZE);
|
||||
|
||||
rc = reverse_box(reverse_key.pbox, key->pbox, AUT64_PBOX_SIZE);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = reverse_box(reverse_key.sbox, key->sbox, AUT64_SBOX_SIZE);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
for(int i = 0; i < AUT64_NUM_ROUNDS; i++) {
|
||||
permute_bytes(&reverse_key, message);
|
||||
message[AUT64_BLOCK_SIZE - 1] = encrypt_compress(&reverse_key, message, (uint8_t)i);
|
||||
message[AUT64_BLOCK_SIZE - 1] = substitute(&reverse_key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
message[AUT64_BLOCK_SIZE - 1] = permute_bits(&reverse_key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
message[AUT64_BLOCK_SIZE - 1] = substitute(&reverse_key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
}
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
|
||||
// Decrypt one 8-byte block in place using the provided validated key.
|
||||
int aut64_decrypt(const struct aut64_key* key, uint8_t* message) {
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
if(!key || !message) {
|
||||
return AUT64_ERR_NULL_POINTER;
|
||||
}
|
||||
int rc = aut64_validate_key(key);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
for(int i = AUT64_NUM_ROUNDS - 1; i >= 0; i--) {
|
||||
message[AUT64_BLOCK_SIZE - 1] = substitute(key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
message[AUT64_BLOCK_SIZE - 1] = permute_bits(key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
message[AUT64_BLOCK_SIZE - 1] = substitute(key, message[AUT64_BLOCK_SIZE - 1]);
|
||||
message[AUT64_BLOCK_SIZE - 1] = decrypt_compress(key, message, (uint8_t)i);
|
||||
permute_bytes(key, message);
|
||||
}
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
|
||||
#ifdef AUT64_PACK_SUPPORT
|
||||
// Serialize a validated key structure into its 16-byte packed format.
|
||||
int aut64_pack(uint8_t* dest, const struct aut64_key* src) {
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
if(!dest || !src) {
|
||||
return AUT64_ERR_NULL_POINTER;
|
||||
}
|
||||
// Validate the key we are about to pack. This prevents producing garbage packed keys.
|
||||
int rc = aut64_validate_key(src);
|
||||
if(rc != AUT64_OK) {
|
||||
return rc;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Initialize the output so callers never observe stale bytes.
|
||||
memset(dest, 0, AUT64_PACKED_KEY_SIZE);
|
||||
|
||||
dest[0] = src->index;
|
||||
|
||||
for(uint8_t i = 0; i < AUT64_KEY_SIZE / 2; i++) {
|
||||
dest[i + 1] = (uint8_t)((src->key[i * 2] << 4) | src->key[i * 2 + 1]);
|
||||
}
|
||||
|
||||
uint32_t pbox = 0;
|
||||
for(uint8_t i = 0; i < AUT64_PBOX_SIZE; i++) {
|
||||
pbox = (pbox << 3) | src->pbox[i];
|
||||
}
|
||||
|
||||
dest[5] = (uint8_t)(pbox >> 16);
|
||||
dest[6] = (uint8_t)((pbox >> 8) & 0xFF);
|
||||
dest[7] = (uint8_t)(pbox & 0xFF);
|
||||
|
||||
for(uint8_t i = 0; i < AUT64_SBOX_SIZE / 2; i++) {
|
||||
dest[i + 8] = (uint8_t)((src->sbox[i * 2] << 4) | src->sbox[i * 2 + 1]);
|
||||
}
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
#endif // AUT64_PACK_SUPPORT
|
||||
|
||||
// Deserialize a 16-byte packed key into a key structure and validate it.
|
||||
int aut64_unpack(struct aut64_key* dest, const uint8_t* src) {
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
if(!dest || !src) {
|
||||
return AUT64_ERR_NULL_POINTER;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Clear the whole struct first, so all fields are in a defined state.
|
||||
*dest = (struct aut64_key){0};
|
||||
|
||||
dest->index = src[0];
|
||||
|
||||
for(uint8_t i = 0; i < AUT64_KEY_SIZE / 2; i++) {
|
||||
dest->key[i * 2] = (uint8_t)(src[i + 1] >> 4);
|
||||
dest->key[i * 2 + 1] = (uint8_t)(src[i + 1] & 0xF);
|
||||
}
|
||||
|
||||
uint32_t pbox = ((uint32_t)src[5] << 16) | ((uint32_t)src[6] << 8) | (uint32_t)src[7];
|
||||
|
||||
for(int8_t i = AUT64_PBOX_SIZE - 1; i >= 0; i--) {
|
||||
dest->pbox[i] = (uint8_t)(pbox & 0x7);
|
||||
pbox >>= 3;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < AUT64_SBOX_SIZE / 2; i++) {
|
||||
dest->sbox[i * 2] = (uint8_t)(src[i + 8] >> 4);
|
||||
dest->sbox[i * 2 + 1] = (uint8_t)(src[i + 8] & 0xF);
|
||||
}
|
||||
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
// Validate what we just unpacked. If invalid, return error.
|
||||
// We do not fix up broken keys silently.
|
||||
int rc = aut64_validate_key(dest);
|
||||
if(rc != AUT64_OK) {
|
||||
return AUT64_ERR_INVALID_PACKED;
|
||||
}
|
||||
#endif
|
||||
|
||||
return AUT64_OK;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// uncomment to activate key validation, index boundary verifications, ...
|
||||
//#define AUT64_ENABLE_VALIDATIONS
|
||||
|
||||
// uncomment to add compilation of aut64_pack (currently unused in the code)
|
||||
//#define AUT64_PACK_SUPPORT
|
||||
|
||||
#define AUT64_NUM_ROUNDS 12
|
||||
#define AUT64_BLOCK_SIZE 8
|
||||
#define AUT64_KEY_SIZE 8
|
||||
#define AUT64_PBOX_SIZE 8
|
||||
#define AUT64_SBOX_SIZE 16
|
||||
#define AUT64_PACKED_KEY_SIZE 16
|
||||
|
||||
// Internal helper table size (offset lookup table).
|
||||
#define AUT64_OFFSET_TABLE_SIZE 256
|
||||
|
||||
// Status codes. Keep it simple and C-friendly.
|
||||
#define AUT64_OK 0
|
||||
#define AUT64_ERR_INVALID_KEY (-1)
|
||||
#define AUT64_ERR_INVALID_PACKED (-2)
|
||||
#define AUT64_ERR_NULL_POINTER (-3)
|
||||
|
||||
struct aut64_key {
|
||||
uint8_t index;
|
||||
uint8_t key[AUT64_KEY_SIZE];
|
||||
uint8_t pbox[AUT64_PBOX_SIZE];
|
||||
uint8_t sbox[AUT64_SBOX_SIZE];
|
||||
};
|
||||
|
||||
#ifdef AUT64_ENABLE_VALIDATIONS
|
||||
// Optional helper if callers want to check keys up-front.
|
||||
int aut64_validate_key(const struct aut64_key* key);
|
||||
#endif
|
||||
|
||||
// Pointers are used for both the key and the message to avoid implicit copies.
|
||||
// The message buffer must be at least AUT64_BLOCK_SIZE bytes.
|
||||
int aut64_encrypt(const struct aut64_key* key, uint8_t* message);
|
||||
int aut64_decrypt(const struct aut64_key* key, uint8_t* message);
|
||||
|
||||
// Packed key buffer must be at least AUT64_PACKED_KEY_SIZE bytes.
|
||||
#ifdef AUT64_PACK_SUPPORT
|
||||
int aut64_pack(uint8_t* dest, const struct aut64_key* src);
|
||||
#endif
|
||||
int aut64_unpack(struct aut64_key* dest, const uint8_t* src);
|
||||
@@ -0,0 +1,768 @@
|
||||
#include "chrysler_v0.h"
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "ChryslerV0"
|
||||
|
||||
#define CHRYSLER_V0_TE_SHORT 0x12C
|
||||
#define CHRYSLER_V0_TE_DELTA 0x96
|
||||
#define CHRYSLER_V0_TE_LONG_A 0xD48
|
||||
#define CHRYSLER_V0_TE_LONG_B 0xE74
|
||||
#define CHRYSLER_V0_TE_LONG_DELTA 0x190
|
||||
#define CHRYSLER_V0_TE_GAP 0x1F40
|
||||
#define CHRYSLER_V0_TE_ONE_SHORT 0x258
|
||||
#define CHRYSLER_V0_FRAME_GAP 0x3CF0
|
||||
#define CHRYSLER_V0_PREAMBLE_PAIRS 24U
|
||||
#define CHRYSLER_V0_DECODE_BIT_COUNT 0x50
|
||||
|
||||
#define CHRYSLER_V0_UPLOAD_CAPACITY 0x200U
|
||||
_Static_assert(
|
||||
CHRYSLER_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"CHRYSLER_V0_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const uint8_t chrysler_v0_xor_table[16] = {
|
||||
0x0F,
|
||||
0x02,
|
||||
0x40,
|
||||
0x0C,
|
||||
0x30,
|
||||
0x0E,
|
||||
0x70,
|
||||
0x08,
|
||||
0x10,
|
||||
0x0A,
|
||||
0x50,
|
||||
0xF4,
|
||||
0x2F,
|
||||
0xF6,
|
||||
0x6F,
|
||||
0xF0,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Chrysler_V0DecoderStepReset = 0,
|
||||
Chrysler_V0DecoderStepSeek = 1,
|
||||
Chrysler_V0DecoderStepData = 2,
|
||||
} Chrysler_V0DecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderChrysler_V0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t packet_bit_count;
|
||||
uint8_t decoded_button;
|
||||
|
||||
uint32_t te_last;
|
||||
uint8_t plain_a[9];
|
||||
uint8_t plain_b[9];
|
||||
|
||||
uint8_t plain_a_present;
|
||||
uint8_t plain_b_present;
|
||||
|
||||
uint8_t check_ok;
|
||||
uint32_t sn_b;
|
||||
|
||||
uint16_t data_2;
|
||||
uint8_t seed;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderChrysler_V0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t tx_button;
|
||||
uint8_t plain_header;
|
||||
|
||||
uint8_t plain_a[9];
|
||||
uint8_t plain_b[9];
|
||||
|
||||
uint16_t data_2;
|
||||
uint8_t seed;
|
||||
};
|
||||
|
||||
static uint8_t chrysler_v0_reverse6(uint32_t value) {
|
||||
uint8_t out = 0;
|
||||
uint8_t bits = 6;
|
||||
|
||||
while(bits--) {
|
||||
out = (uint8_t)((out << 1U) | (value & 1U));
|
||||
value >>= 1U;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void
|
||||
chrysler_v0_transform_block(const uint8_t in[9], uint8_t out[9], uint32_t key, uint8_t button) {
|
||||
uint8_t mask = chrysler_v0_xor_table[key & 0x0FU];
|
||||
if(button == 1U) {
|
||||
mask ^= (key & 1U) ? 0xF0U : 0x0FU;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < 9; i++) {
|
||||
out[i] = in[i] ^ mask;
|
||||
}
|
||||
}
|
||||
|
||||
static bool chrysler_v0_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, CHRYSLER_V0_TE_SHORT) <= CHRYSLER_V0_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool chrysler_v0_is_long_mark(uint32_t duration) {
|
||||
return (DURATION_DIFF(duration, CHRYSLER_V0_TE_LONG_A) <= CHRYSLER_V0_TE_LONG_DELTA) ||
|
||||
(DURATION_DIFF(duration, CHRYSLER_V0_TE_LONG_B) <= CHRYSLER_V0_TE_LONG_DELTA);
|
||||
}
|
||||
|
||||
static const char* chrysler_v0_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 1:
|
||||
return "Lock";
|
||||
case 2:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t chrysler_v0_get_sn_b(const SubGhzProtocolDecoderChrysler_V0* instance) {
|
||||
return instance->sn_b;
|
||||
}
|
||||
|
||||
static void chrysler_v0_set_sn_b(SubGhzProtocolDecoderChrysler_V0* instance, uint32_t sn_b) {
|
||||
instance->sn_b = sn_b;
|
||||
}
|
||||
|
||||
static void chrysler_v0_decode_packet(SubGhzProtocolDecoderChrysler_V0* instance) {
|
||||
uint8_t key[8];
|
||||
uint8_t encoded[9];
|
||||
uint8_t decoded[9];
|
||||
const uint16_t key2 = instance->data_2;
|
||||
|
||||
pp_u64_to_bytes_be(instance->generic.data, key);
|
||||
instance->seed = chrysler_v0_reverse6(key[0] >> 2U);
|
||||
|
||||
const uint8_t b1_xor_b6 = key[6] ^ key[1];
|
||||
const bool msb_set = (key[0] & 0x80U) != 0U;
|
||||
|
||||
if(msb_set) {
|
||||
const uint8_t key2_low = (uint8_t)(key2 & 0xFFU);
|
||||
instance->check_ok = (key[1] == key[5]) && (b1_xor_b6 == 0x62U);
|
||||
instance->decoded_button = (((uint8_t)(key2_low ^ key[4])) == 0x10U) ? 2U : 1U;
|
||||
} else {
|
||||
instance->check_ok = 0U;
|
||||
instance->decoded_button = 1U;
|
||||
|
||||
if(((uint8_t)(key[1] ^ 0xC3U)) == key[5]) {
|
||||
if(b1_xor_b6 == 0x04U) {
|
||||
instance->check_ok = 1U;
|
||||
} else {
|
||||
instance->check_ok = (b1_xor_b6 == 0x08U);
|
||||
if(b1_xor_b6 == 0x08U) {
|
||||
instance->decoded_button = 2U;
|
||||
} else {
|
||||
FURI_LOG_D(TAG, "BtnDetect: unknown b1^b6=%02X (MSB=0)", b1_xor_b6);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(b1_xor_b6 == 0x08U) {
|
||||
instance->decoded_button = 2U;
|
||||
} else if(b1_xor_b6 != 0x04U) {
|
||||
FURI_LOG_D(TAG, "BtnDetect: unknown b1^b6=%02X (MSB=0)", b1_xor_b6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
encoded[0] = key[1];
|
||||
encoded[1] = key[2];
|
||||
encoded[2] = key[3];
|
||||
encoded[3] = key[4];
|
||||
encoded[4] = key[5];
|
||||
encoded[5] = key[6];
|
||||
encoded[6] = key[7];
|
||||
encoded[7] = (uint8_t)(key2 >> 8U);
|
||||
encoded[8] = (uint8_t)(key2 & 0xFFU);
|
||||
chrysler_v0_transform_block(encoded, decoded, instance->seed, instance->decoded_button);
|
||||
|
||||
if(instance->seed & 1U) {
|
||||
memcpy(instance->plain_b, decoded, sizeof(instance->plain_b));
|
||||
instance->plain_b_present = 1U;
|
||||
|
||||
const uint32_t sn_b = ((uint32_t)decoded[0] << 24U) | ((uint32_t)decoded[1] << 16U) |
|
||||
((uint32_t)decoded[2] << 8U) | (uint32_t)decoded[7];
|
||||
chrysler_v0_set_sn_b(instance, sn_b);
|
||||
} else {
|
||||
memcpy(instance->plain_a, decoded, sizeof(instance->plain_a));
|
||||
instance->plain_a_present = 1U;
|
||||
|
||||
instance->generic.cnt = ((uint32_t)decoded[0] << 24U) |
|
||||
((uint32_t)decoded[1] << 16U) |
|
||||
((uint32_t)decoded[2] << 8U) | (uint32_t)decoded[3];
|
||||
}
|
||||
|
||||
instance->generic.btn = instance->decoded_button;
|
||||
}
|
||||
|
||||
static void chrysler_v0_decoder_commit(SubGhzProtocolDecoderChrysler_V0* instance) {
|
||||
instance->packet_bit_count = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
instance->decoder.decode_count_bit = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
instance->generic.data_count_bit = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
chrysler_v0_decode_packet(instance);
|
||||
|
||||
if(instance->check_ok && instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static uint8_t chrysler_v0_payload_get_bit(const uint8_t payload[10], uint8_t index) {
|
||||
const uint8_t byte = payload[index >> 3U];
|
||||
const uint8_t shift = 7U - (index & 7U);
|
||||
return (byte >> shift) & 1U;
|
||||
}
|
||||
|
||||
static void chrysler_v0_build_payload(
|
||||
const uint8_t plain[9],
|
||||
uint8_t counter,
|
||||
uint8_t button,
|
||||
uint8_t header_low2,
|
||||
uint8_t out[10]) {
|
||||
uint8_t transformed[9];
|
||||
chrysler_v0_transform_block(plain, transformed, counter, button);
|
||||
|
||||
out[0] = (uint8_t)((chrysler_v0_reverse6(counter) << 2U) | (header_low2 & 0x03U));
|
||||
memcpy(&out[1], transformed, sizeof(transformed));
|
||||
}
|
||||
|
||||
static size_t chrysler_v0_build_upload(
|
||||
SubGhzProtocolEncoderChrysler_V0* instance,
|
||||
const uint8_t payload_a[10],
|
||||
const uint8_t payload_b[10]) {
|
||||
size_t i = 0;
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
const size_t cap = CHRYSLER_V0_UPLOAD_CAPACITY;
|
||||
|
||||
for(size_t preamble = 0; preamble < CHRYSLER_V0_PREAMBLE_PAIRS; preamble++) {
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_TE_LONG_B);
|
||||
}
|
||||
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_FRAME_GAP);
|
||||
|
||||
for(uint8_t bit = 0; bit < 80; bit++) {
|
||||
const bool value = chrysler_v0_payload_get_bit(payload_a, bit);
|
||||
i = pp_emit(upload, i, cap, true, value ? CHRYSLER_V0_TE_ONE_SHORT : CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, value ? CHRYSLER_V0_TE_LONG_A : CHRYSLER_V0_TE_LONG_B);
|
||||
}
|
||||
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_FRAME_GAP);
|
||||
|
||||
for(size_t preamble = 0; preamble < CHRYSLER_V0_PREAMBLE_PAIRS; preamble++) {
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_TE_LONG_B);
|
||||
}
|
||||
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_FRAME_GAP);
|
||||
|
||||
for(uint8_t bit = 0; bit < 80; bit++) {
|
||||
const bool value = chrysler_v0_payload_get_bit(payload_b, bit);
|
||||
i = pp_emit(upload, i, cap, true, value ? CHRYSLER_V0_TE_ONE_SHORT : CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, value ? CHRYSLER_V0_TE_LONG_A : CHRYSLER_V0_TE_LONG_B);
|
||||
}
|
||||
|
||||
i = pp_emit(upload, i, cap, true, CHRYSLER_V0_TE_SHORT);
|
||||
i = pp_emit(upload, i, cap, false, CHRYSLER_V0_FRAME_GAP);
|
||||
|
||||
instance->encoder.size_upload = i;
|
||||
return i;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_chrysler_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_chrysler_v0_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_chrysler_v0_feed,
|
||||
.reset = subghz_protocol_decoder_chrysler_v0_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = subghz_protocol_decoder_chrysler_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_chrysler_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_chrysler_v0_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_chrysler_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_chrysler_v0_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_chrysler_v0_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_chrysler_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol chrysler_protocol_v0 = {
|
||||
.name = CHRYSLER_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Load
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
| SubGhzProtocolFlag_Send
|
||||
#endif
|
||||
,
|
||||
.decoder = &subghz_protocol_chrysler_v0_decoder,
|
||||
.encoder = &subghz_protocol_chrysler_v0_encoder,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderChrysler_V0* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderChrysler_V0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &chrysler_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 2;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderChrysler_V0* instance = context;
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, CHRYSLER_V0_DECODE_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
uint16_t key2 = 0U;
|
||||
if(!flipper_format_read_hex(flipper_format, "Key_2", (uint8_t*)&key2, 2)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
key2 = __builtin_bswap16(key2);
|
||||
instance->data_2 = key2;
|
||||
|
||||
uint8_t key[8];
|
||||
pp_u64_to_bytes_be(instance->generic.data, key);
|
||||
const uint8_t b0 = key[0];
|
||||
|
||||
instance->seed = chrysler_v0_reverse6(((uint32_t)(instance->generic.data >> 56U)) >> 2U);
|
||||
instance->plain_header = (uint8_t)((instance->generic.data >> 56U) & 0x03U);
|
||||
|
||||
if((b0 & 0x80U) == 0U) {
|
||||
instance->tx_button = (((uint8_t)(key[1] ^ key[6])) == 0x08U) ? 2U : 1U;
|
||||
} else {
|
||||
instance->tx_button = (((uint8_t)(key2 & 0xFFU) ^ key[4]) == 0x10U) ? 2U : 1U;
|
||||
}
|
||||
|
||||
const uint8_t original_button = instance->tx_button;
|
||||
|
||||
uint8_t encoded[9];
|
||||
uint8_t generated[9];
|
||||
encoded[0] = key[1];
|
||||
encoded[1] = key[2];
|
||||
encoded[2] = key[3];
|
||||
encoded[3] = key[4];
|
||||
encoded[4] = key[5];
|
||||
encoded[5] = key[6];
|
||||
encoded[6] = key[7];
|
||||
encoded[7] = (uint8_t)(key2 >> 8U);
|
||||
encoded[8] = (uint8_t)(key2 & 0xFFU);
|
||||
chrysler_v0_transform_block(encoded, generated, instance->seed, instance->tx_button);
|
||||
|
||||
if(flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_hex(flipper_format, "Plain_A", instance->plain_a, 9)) {
|
||||
if(!(flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_hex(flipper_format, "Plain_B", instance->plain_b, 9))) {
|
||||
memcpy(instance->plain_b, instance->plain_a, sizeof(instance->plain_b));
|
||||
}
|
||||
} else if(
|
||||
flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_hex(flipper_format, "Plain_B", instance->plain_b, 9)) {
|
||||
memcpy(instance->plain_a, instance->plain_b, sizeof(instance->plain_a));
|
||||
} else {
|
||||
memcpy(instance->plain_a, generated, sizeof(instance->plain_a));
|
||||
memcpy(instance->plain_b, generated, sizeof(instance->plain_b));
|
||||
}
|
||||
|
||||
uint32_t btn_u32 = 0;
|
||||
uint32_t cnt_u32 = instance->seed & 0x3FU;
|
||||
pp_encoder_read_fields(flipper_format, NULL, &btn_u32, &cnt_u32, NULL);
|
||||
|
||||
uint8_t tx_button = original_button;
|
||||
if(btn_u32 == 1U || btn_u32 == 2U) {
|
||||
tx_button = (uint8_t)btn_u32;
|
||||
}
|
||||
|
||||
instance->tx_button = tx_button;
|
||||
if(tx_button != original_button) {
|
||||
instance->plain_a[5] ^= 0x0CU;
|
||||
instance->plain_b[3] ^= 0x30U;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 2);
|
||||
|
||||
uint32_t counter = cnt_u32 & 0x3FU;
|
||||
|
||||
uint8_t counter_a = (uint8_t)(counter & 0x3FU);
|
||||
if(counter_a & 1U) {
|
||||
counter_a = (uint8_t)((counter_a - 1U) & 0x3FU);
|
||||
}
|
||||
instance->seed = counter_a;
|
||||
const uint8_t counter_b = (counter_a == 0U) ? 0x3FU : (uint8_t)(counter_a - 1U);
|
||||
|
||||
uint8_t payload_a[10];
|
||||
uint8_t payload_b[10];
|
||||
chrysler_v0_build_payload(
|
||||
instance->plain_a, counter_a, instance->tx_button, instance->plain_header, payload_a);
|
||||
chrysler_v0_build_payload(
|
||||
instance->plain_b, counter_b, instance->tx_button, instance->plain_header, payload_b);
|
||||
|
||||
pp_encoder_buffer_ensure(instance, CHRYSLER_V0_UPLOAD_CAPACITY);
|
||||
chrysler_v0_build_upload(instance, payload_a, payload_b);
|
||||
|
||||
instance->generic.data = pp_bytes_to_u64_be(payload_a);
|
||||
instance->data_2 = ((uint16_t)payload_a[8] << 8U) | payload_a[9];
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, FF_KEY, payload_a, 8)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
uint16_t key2_out = __builtin_bswap16(instance->data_2);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key_2", (uint8_t*)&key2_out, 2)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
return status;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderChrysler_V0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &chrysler_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance = context;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->data_2 = 0;
|
||||
instance->seed = 0;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->packet_bit_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->plain_a_present = 0;
|
||||
instance->plain_b_present = 0;
|
||||
instance->sn_b = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Chrysler_V0DecoderStepReset:
|
||||
if(level && chrysler_v0_is_short(duration)) {
|
||||
instance->packet_bit_count = 0;
|
||||
instance->te_last = duration;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepSeek;
|
||||
}
|
||||
break;
|
||||
|
||||
case Chrysler_V0DecoderStepSeek:
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
|
||||
if(chrysler_v0_is_long_mark(duration)) {
|
||||
if(chrysler_v0_is_short(instance->te_last)) {
|
||||
instance->packet_bit_count++;
|
||||
} else if(instance->packet_bit_count > 0x0F) {
|
||||
instance->data_2 = 0;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepData;
|
||||
instance->decoder.decode_data = 1;
|
||||
instance->decoder.decode_count_bit = 1;
|
||||
} else {
|
||||
instance->packet_bit_count = 0;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepSeek;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if((duration > CHRYSLER_V0_TE_GAP) && (instance->packet_bit_count > 0x0F)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->data_2 = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepData;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->packet_bit_count = 0;
|
||||
break;
|
||||
|
||||
case Chrysler_V0DecoderStepData: {
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t count = instance->decoder.decode_count_bit;
|
||||
if(duration > CHRYSLER_V0_TE_GAP) {
|
||||
if(count > 0x4FU) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
chrysler_v0_decoder_commit(instance);
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->packet_bit_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t bit_value = 0;
|
||||
if(instance->te_last < CHRYSLER_V0_TE_SHORT) {
|
||||
if(!chrysler_v0_is_short(instance->te_last) || !chrysler_v0_is_long_mark(duration)) {
|
||||
if(count > 0x4FU) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
chrysler_v0_decoder_commit(instance);
|
||||
}
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->packet_bit_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_value = 1U;
|
||||
} else {
|
||||
if(instance->te_last > 0x2EEU || !chrysler_v0_is_long_mark(duration)) {
|
||||
if(count > 0x4FU) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
chrysler_v0_decoder_commit(instance);
|
||||
}
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->packet_bit_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
bit_value = chrysler_v0_is_short(instance->te_last) ? 1U : 0U;
|
||||
}
|
||||
|
||||
const uint8_t bit = bit_value ^ 1U;
|
||||
const uint8_t new_count = (uint8_t)(count + 1U);
|
||||
if(count <= 0x3FU) {
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1U) | bit;
|
||||
instance->decoder.decode_count_bit = new_count;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->data_2 = (uint16_t)((instance->data_2 << 1U) | bit);
|
||||
instance->decoder.decode_count_bit = new_count;
|
||||
if(new_count != CHRYSLER_V0_DECODE_BIT_COUNT) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
chrysler_v0_decoder_commit(instance);
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->data_2 = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
instance->packet_bit_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
instance->decoder.parser_step = Chrysler_V0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance = context;
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
const uint16_t key2 = __builtin_bswap16(instance->data_2);
|
||||
if(!flipper_format_write_hex(flipper_format, "Key_2", (const uint8_t*)&key2, 2)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
if(instance->plain_a_present) {
|
||||
if(!flipper_format_write_hex(flipper_format, "Plain_A", instance->plain_a, 9)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->plain_b_present) {
|
||||
if(!flipper_format_write_hex(flipper_format, "Plain_B", instance->plain_b, 9)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(!instance->plain_a_present && !instance->plain_b_present) {
|
||||
pp_write_display(
|
||||
flipper_format,
|
||||
instance->generic.protocol_name,
|
||||
chrysler_v0_get_button_name(instance->decoded_button));
|
||||
}
|
||||
|
||||
const uint32_t serial_value = instance->plain_b_present ? chrysler_v0_get_sn_b(instance) :
|
||||
instance->generic.cnt;
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, serial_value);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->decoded_button);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->seed);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance = context;
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
uint16_t key2 = 0U;
|
||||
if(!flipper_format_read_hex(flipper_format, "Key_2", (uint8_t*)&key2, 2)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
key2 = __builtin_bswap16(key2);
|
||||
instance->data_2 = key2;
|
||||
instance->packet_bit_count = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
instance->decoder.decode_count_bit = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
instance->generic.data_count_bit = CHRYSLER_V0_DECODE_BIT_COUNT;
|
||||
|
||||
chrysler_v0_decode_packet(instance);
|
||||
|
||||
if(flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_hex(flipper_format, "Plain_A", instance->plain_a, 9)) {
|
||||
instance->plain_a_present = 1U;
|
||||
uint32_t sn_a = 0;
|
||||
memcpy(&sn_a, instance->plain_a, sizeof(sn_a));
|
||||
instance->generic.cnt = __builtin_bswap32(sn_a);
|
||||
}
|
||||
|
||||
if(flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_hex(flipper_format, "Plain_B", instance->plain_b, 9)) {
|
||||
instance->plain_b_present = 1U;
|
||||
const uint32_t sn_b =
|
||||
((uint32_t)instance->plain_b[0] << 24U) | ((uint32_t)instance->plain_b[1] << 16U) |
|
||||
((uint32_t)instance->plain_b[2] << 8U) | (uint32_t)instance->plain_b[7];
|
||||
chrysler_v0_set_sn_b(instance, sn_b);
|
||||
}
|
||||
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderChrysler_V0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n%016llX%04X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->packet_bit_count,
|
||||
instance->generic.data,
|
||||
instance->data_2);
|
||||
|
||||
if(instance->plain_a_present) {
|
||||
if(instance->plain_b_present) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"SnA:%08lX\r\nSnB:%08lX\r\n",
|
||||
instance->generic.cnt,
|
||||
chrysler_v0_get_sn_b(instance));
|
||||
} else {
|
||||
furi_string_cat_printf(output, "SnA:%08lX\r\n", instance->generic.cnt);
|
||||
}
|
||||
} else if(instance->plain_b_present) {
|
||||
furi_string_cat_printf(output, "SnB:%08lX\r\n", chrysler_v0_get_sn_b(instance));
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"Btn:%02X [%s] Cnt:%02X\r\nChk:%s",
|
||||
instance->decoded_button,
|
||||
chrysler_v0_get_button_name(instance->decoded_button),
|
||||
instance->seed,
|
||||
instance->check_ok ? "OK" : "ERR");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define CHRYSLER_PROTOCOL_V0_NAME "Chrysler V0"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderChrysler_V0 SubGhzProtocolDecoderChrysler_V0;
|
||||
typedef struct SubGhzProtocolEncoderChrysler_V0 SubGhzProtocolEncoderChrysler_V0;
|
||||
|
||||
extern const SubGhzProtocol chrysler_protocol_v0;
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_chrysler_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_chrysler_v0_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_chrysler_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_chrysler_v0_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
#endif
|
||||
@@ -0,0 +1,556 @@
|
||||
#include "fiat_v0.h"
|
||||
#include "protocols_common.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define TAG "FiatProtocolV0"
|
||||
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
|
||||
#define FIAT_V0_PREAMBLE_PAIRS 150
|
||||
#define FIAT_V0_GAP_US 800
|
||||
#define FIAT_V0_TOTAL_BURSTS 3
|
||||
#define FIAT_V0_INTER_BURST_GAP 25000
|
||||
#define FIAT_V0_UPLOAD_CAPACITY 1328U
|
||||
_Static_assert(
|
||||
FIAT_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"FIAT_V0_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_v0_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderFiatV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
uint32_t data_low;
|
||||
uint32_t data_high;
|
||||
uint8_t bit_count;
|
||||
uint32_t hop;
|
||||
uint32_t fix;
|
||||
uint8_t endbyte;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FiatV0DecoderStepReset = 0,
|
||||
FiatV0DecoderStepPreamble = 1,
|
||||
FiatV0DecoderStepData = 2,
|
||||
} FiatV0DecoderStep;
|
||||
|
||||
static void fiat_v0_finish_packet(struct SubGhzProtocolDecoderFiatV0* instance) {
|
||||
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
|
||||
instance->generic.data_count_bit = 71;
|
||||
instance->generic.serial = instance->fix;
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
|
||||
if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context);
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
}
|
||||
|
||||
struct SubGhzProtocolEncoderFiatV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t hop;
|
||||
uint32_t fix;
|
||||
uint8_t endbyte;
|
||||
};
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_fiat_v0_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol fiat_protocol_v0 = {
|
||||
.name = FIAT_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_fiat_v0_decoder,
|
||||
.encoder = &subghz_protocol_fiat_v0_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &fiat_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
|
||||
furi_check(instance);
|
||||
LevelDuration* up = instance->encoder.upload;
|
||||
if(up == NULL) return;
|
||||
|
||||
size_t index = 0;
|
||||
const size_t cap = FIAT_V0_UPLOAD_CAPACITY;
|
||||
uint32_t te_short = subghz_protocol_fiat_v0_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_fiat_v0_const.te_long;
|
||||
|
||||
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
|
||||
uint8_t endbyte_to_send = instance->endbyte >> 1;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Building upload: hop=0x%08lX fix=0x%08lX endbyte=0x%02X send6=0x%02X",
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->endbyte,
|
||||
endbyte_to_send);
|
||||
|
||||
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
|
||||
if(burst > 0) {
|
||||
index = pp_emit(up, index, cap, false, FIAT_V0_INTER_BURST_GAP);
|
||||
furi_check(index <= cap);
|
||||
}
|
||||
|
||||
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
}
|
||||
if(index > 0) up[index - 1] = level_duration_make(false, FIAT_V0_GAP_US);
|
||||
|
||||
bool first_bit = (data >> 63) & 1;
|
||||
if(first_bit) {
|
||||
index = pp_emit(up, index, cap, true, te_long);
|
||||
} else {
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
index = pp_emit(up, index, cap, false, te_long);
|
||||
}
|
||||
bool prev_bit = first_bit;
|
||||
|
||||
for(int bit = 62; bit >= 0; bit--) {
|
||||
bool curr_bit = (data >> bit) & 1;
|
||||
if(!prev_bit && !curr_bit) {
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
index = pp_emit(up, index, cap, true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
index = pp_emit(up, index, cap, false, te_long);
|
||||
} else {
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
furi_check(index <= cap);
|
||||
}
|
||||
|
||||
for(int bit = 5; bit >= 0; bit--) {
|
||||
bool curr_bit = (endbyte_to_send >> bit) & 1;
|
||||
if(!prev_bit && !curr_bit) {
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
index = pp_emit(up, index, cap, true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
index = pp_emit(up, index, cap, false, te_long);
|
||||
} else {
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
index = pp_emit(up, index, cap, true, te_short);
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
furi_check(index <= cap);
|
||||
}
|
||||
|
||||
if(prev_bit) {
|
||||
index = pp_emit(up, index, cap, false, te_short);
|
||||
}
|
||||
index = pp_emit(up, index, cap, false, te_short * 8);
|
||||
furi_check(index <= cap);
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
static const uint16_t allowed_bits[] = {64U, 71U};
|
||||
uint32_t bit_count = 0;
|
||||
if(pp_encoder_read_bit(flipper_format, allowed_bits, 2, &bit_count) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
instance->generic.data_count_bit = 71; // legacy default for garbage Bit values
|
||||
} else {
|
||||
instance->generic.data_count_bit = bit_count;
|
||||
}
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
instance->generic.data = key;
|
||||
instance->hop = (uint32_t)(key >> 32);
|
||||
instance->fix = (uint32_t)(key & 0xFFFFFFFFU);
|
||||
|
||||
uint32_t eb_read = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
bool have_endbyte = flipper_format_read_uint32(flipper_format, "EndByte", &eb_read, 1);
|
||||
|
||||
uint32_t btn_u32 = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
pp_encoder_read_fields(flipper_format, NULL, &btn_u32, NULL, NULL);
|
||||
|
||||
if(have_endbyte) {
|
||||
instance->endbyte = (uint8_t)(eb_read & 0x7FU);
|
||||
} else {
|
||||
instance->endbyte = (uint8_t)(btn_u32 & 0x7FU);
|
||||
}
|
||||
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
instance->generic.serial = instance->fix;
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
pp_encoder_buffer_ensure(instance, FIAT_V0_UPLOAD_CAPACITY);
|
||||
subghz_protocol_encoder_fiat_v0_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
#endif
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &fiat_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->hop = 0;
|
||||
instance->fix = 0;
|
||||
instance->endbyte = 0;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short;
|
||||
uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long;
|
||||
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta;
|
||||
uint32_t gap_threshold = FIAT_V0_GAP_US;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FiatV0DecoderStepReset:
|
||||
if(!level) return;
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->decoder.parser_step = FiatV0DecoderStepPreamble;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV0DecoderStepPreamble:
|
||||
if(level) {
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->preamble_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
|
||||
if(diff < te_delta) {
|
||||
instance->preamble_count++;
|
||||
} else {
|
||||
if(instance->preamble_count >= FIAT_V0_PREAMBLE_PAIRS) {
|
||||
if(duration < gap_threshold) {
|
||||
diff = gap_threshold - duration;
|
||||
} else {
|
||||
diff = duration - gap_threshold;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->decoder.parser_step = FiatV0DecoderStepData;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
}
|
||||
|
||||
if(instance->preamble_count >= FIAT_V0_PREAMBLE_PAIRS &&
|
||||
instance->decoder.parser_step == FiatV0DecoderStepPreamble) {
|
||||
if(duration < gap_threshold) {
|
||||
diff = gap_threshold - duration;
|
||||
} else {
|
||||
diff = duration - gap_threshold;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->decoder.parser_step = FiatV0DecoderStepData;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV0DecoderStepData: {
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
}
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
if(duration < te_long) {
|
||||
diff = te_long - duration;
|
||||
} else {
|
||||
diff = duration - te_long;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit_bool;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state,
|
||||
event,
|
||||
&instance->manchester_state,
|
||||
&data_bit_bool)) {
|
||||
uint32_t new_bit = data_bit_bool ? 1 : 0;
|
||||
uint32_t carry = (instance->data_low >> 31) & 1;
|
||||
instance->data_low = (instance->data_low << 1) | new_bit;
|
||||
instance->data_high = (instance->data_high << 1) | carry;
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count == 64) {
|
||||
instance->fix = instance->data_low;
|
||||
instance->hop = instance->data_high;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
}
|
||||
if(instance->bit_count == 0x47) {
|
||||
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
|
||||
fiat_v0_finish_packet(instance);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(instance->bit_count == 0x47) {
|
||||
instance->endbyte = (uint8_t)(instance->data_low & 0x3F);
|
||||
fiat_v0_finish_packet(instance);
|
||||
} else if(instance->bit_count < 64) {
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_FREQUENCY, &preset->frequency, 1))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, FF_PRESET, furi_string_get_cstr(preset->name)))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, FF_PROTOCOL, instance->generic.protocol_name))
|
||||
break;
|
||||
|
||||
uint32_t bits = instance->generic.data_count_bit;
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_BIT, &bits, 1)) break;
|
||||
|
||||
char key_str[20];
|
||||
snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, FF_KEY, key_str)) break;
|
||||
|
||||
if(pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->fix,
|
||||
instance->endbyte,
|
||||
instance->hop,
|
||||
0) != SubGhzProtocolStatusOk)
|
||||
break;
|
||||
|
||||
uint32_t endbyte_ff = instance->endbyte;
|
||||
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) break;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Hop:%08lX\r\n"
|
||||
"Sn:%08lX\r\n"
|
||||
"EndByte:%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->endbyte & 0x3F);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
|
||||
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
|
||||
|
||||
extern const SubGhzProtocol fiat_protocol_v0;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_v0_free(void* context);
|
||||
void subghz_protocol_decoder_fiat_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -0,0 +1,529 @@
|
||||
#include "fiat_v1.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "FiatProtocolV1"
|
||||
|
||||
// Magneti Marelli BSI keyfob protocol (PCF7946)
|
||||
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
||||
//
|
||||
// RF: 433.92 MHz, Manchester encoding
|
||||
// Two timing variants with identical frame structure:
|
||||
// Type A (e.g. Panda): te_short ~260us, te_long ~520us
|
||||
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
||||
// TE is auto-detected from preamble pulse averaging.
|
||||
//
|
||||
// Frame layout (104 bits = 13 bytes):
|
||||
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||
// Bytes 2-5: Serial (32 bits)
|
||||
// Byte 6: [Button:4 | Epoch:4]
|
||||
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||
// Bytes 8-12: Encrypted payload (40 bits)
|
||||
//
|
||||
// Original implementation by @lupettohf
|
||||
|
||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 35
|
||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 450
|
||||
#define FIAT_MARELLI_PREAMBLE_MIN 48
|
||||
#define FIAT_MARELLI_MAX_DATA_BITS 104
|
||||
#define FIAT_MARELLI_MIN_DATA_BITS 104
|
||||
#define FIAT_MARELLI_GAP_TE_MULT 4
|
||||
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4
|
||||
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12
|
||||
#define FIAT_MARELLI_RETX_GAP_MIN 5000
|
||||
#define FIAT_MARELLI_RETX_SYNC_MIN 400
|
||||
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
|
||||
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||
|
||||
#define fiat_marelli_crc8(data, len) subghz_protocol_blocks_crc8((data), (len), 0x01, 0x03)
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
||||
.te_short = 260,
|
||||
.te_long = 520,
|
||||
.te_delta = 80,
|
||||
.min_count_bit_for_found = FIAT_MARELLI_MIN_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FiatMarelliDecoderStepReset = 0,
|
||||
FiatMarelliDecoderStepPreamble = 1,
|
||||
FiatMarelliDecoderStepSync = 2,
|
||||
FiatMarelliDecoderStepData = 3,
|
||||
} FiatMarelliDecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderFiatMarelli {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_data[13];
|
||||
uint8_t bit_count;
|
||||
uint32_t extra_data;
|
||||
|
||||
uint32_t te_last;
|
||||
uint32_t te_sum;
|
||||
uint16_t te_count;
|
||||
uint32_t te_detected;
|
||||
};
|
||||
|
||||
static void fiat_marelli_set_state(
|
||||
SubGhzProtocolDecoderFiatMarelli* instance,
|
||||
FiatMarelliDecoderStep new_state,
|
||||
const char* reason) {
|
||||
UNUSED(reason);
|
||||
instance->decoder_state = new_state;
|
||||
}
|
||||
|
||||
static bool fiat_marelli_get_raw_bit(const uint8_t* raw, uint8_t bit_index) {
|
||||
return (raw[bit_index / 8] >> (7 - (bit_index % 8))) & 1U;
|
||||
}
|
||||
|
||||
static void fiat_marelli_set_raw_bit(uint8_t* raw, uint8_t bit_index, bool value) {
|
||||
uint8_t byte_idx = bit_index / 8;
|
||||
uint8_t mask = 1U << (7 - (bit_index % 8));
|
||||
if(value) {
|
||||
raw[byte_idx] |= mask;
|
||||
} else {
|
||||
raw[byte_idx] &= (uint8_t)(~mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void fiat_marelli_rebuild_data_words_from_raw(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||
instance->generic.data = 0;
|
||||
instance->extra_data = 0;
|
||||
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
instance->generic.data = (instance->generic.data << 1) |
|
||||
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
|
||||
}
|
||||
|
||||
for(uint8_t i = 64; i < FIAT_MARELLI_MAX_DATA_BITS; i++) {
|
||||
instance->extra_data = (instance->extra_data << 1) |
|
||||
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
|
||||
}
|
||||
|
||||
instance->bit_count = FIAT_MARELLI_MAX_DATA_BITS;
|
||||
instance->generic.data_count_bit = FIAT_MARELLI_MAX_DATA_BITS;
|
||||
}
|
||||
|
||||
static bool fiat_marelli_try_recover_tail_bits(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(instance->bit_count < (FIAT_MARELLI_MAX_DATA_BITS - 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t missing_bits = FIAT_MARELLI_MAX_DATA_BITS - instance->bit_count;
|
||||
uint8_t variants = 1U << missing_bits;
|
||||
uint8_t match_count = 0;
|
||||
uint8_t matched_variant = 0;
|
||||
|
||||
for(uint8_t variant = 0; variant < variants; variant++) {
|
||||
uint8_t trial[13];
|
||||
memcpy(trial, instance->raw_data, sizeof(trial));
|
||||
|
||||
for(uint8_t i = 0; i < missing_bits; i++) {
|
||||
bool bit = ((variant >> (missing_bits - 1 - i)) & 1U) != 0;
|
||||
fiat_marelli_set_raw_bit(trial, instance->bit_count + i, bit);
|
||||
}
|
||||
|
||||
uint8_t calc = fiat_marelli_crc8(trial, 12);
|
||||
if(calc == trial[12]) {
|
||||
match_count++;
|
||||
matched_variant = variant;
|
||||
}
|
||||
}
|
||||
|
||||
if(match_count != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < missing_bits; i++) {
|
||||
bool bit = ((matched_variant >> (missing_bits - 1 - i)) & 1U) != 0;
|
||||
fiat_marelli_set_raw_bit(instance->raw_data, instance->bit_count + i, bit);
|
||||
}
|
||||
|
||||
fiat_marelli_rebuild_data_words_from_raw(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepData, "sync accepted");
|
||||
}
|
||||
|
||||
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
|
||||
uint64_t key = instance->generic.data;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
uint8_t extra_bits =
|
||||
(instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0;
|
||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||
uint8_t byte_idx = 8 + (i / 8);
|
||||
uint8_t bit_pos = 7 - (i % 8);
|
||||
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
|
||||
instance->raw_data[byte_idx] |= (1U << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
|
||||
if(instance->bit_count >= 56) {
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) | ((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) | ((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* fiat_marelli_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x8:
|
||||
case 0x7:
|
||||
return "Lock";
|
||||
case 0x0:
|
||||
case 0xB:
|
||||
return "Unlock";
|
||||
case 0xD:
|
||||
return "Trunk";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_marelli_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_fiat_marelli_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_marelli_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_marelli_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_marelli_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_marelli_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_marelli_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_marelli_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol fiat_v1_protocol = {
|
||||
.name = FIAT_MARELLI_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_fiat_marelli_decoder,
|
||||
.encoder = &subghz_protocol_fiat_marelli_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderFiatMarelli));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &fiat_v1_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_marelli_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "decoder reset");
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->te_last = 0;
|
||||
instance->te_sum = 0;
|
||||
instance->te_count = 0;
|
||||
instance->te_detected = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
|
||||
uint32_t te_short = instance->te_detected ?
|
||||
instance->te_detected :
|
||||
(uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
||||
uint32_t te_long = te_short * 2;
|
||||
uint32_t te_delta = te_short / 2;
|
||||
if(te_delta < 30) te_delta = 30;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case FiatMarelliDecoderStepReset:
|
||||
if(level) {
|
||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepPreamble, "preamble start");
|
||||
instance->preamble_count = 1;
|
||||
instance->te_sum = duration;
|
||||
instance->te_count = 1;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatMarelliDecoderStepPreamble:
|
||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
instance->preamble_count++;
|
||||
instance->te_sum += duration;
|
||||
instance->te_count++;
|
||||
instance->te_last = duration;
|
||||
} else if(!level) {
|
||||
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
|
||||
instance->te_detected = instance->te_sum / instance->te_count;
|
||||
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
|
||||
|
||||
if(duration > gap_threshold) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepSync, "gap detected");
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "gap too short");
|
||||
}
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "preamble too short");
|
||||
}
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "invalid preamble pulse");
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatMarelliDecoderStepSync: {
|
||||
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
|
||||
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
|
||||
|
||||
if(level && duration >= sync_min && duration <= sync_max) {
|
||||
fiat_marelli_prepare_data(instance);
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "sync timing mismatch");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FiatMarelliDecoderStepData: {
|
||||
bool frame_complete = false;
|
||||
const SubGhzBlockConst fiat_v1_dyn = {
|
||||
.te_short = (uint16_t)te_short,
|
||||
.te_long = (uint16_t)te_long,
|
||||
.te_delta = (uint16_t)te_delta,
|
||||
.min_count_bit_for_found = 0,
|
||||
};
|
||||
ManchesterEvent event = pp_manchester_event(duration, level, &fiat_v1_dyn);
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit = false;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
uint32_t new_bit = data_bit ? 1U : 0U;
|
||||
|
||||
if(instance->bit_count < FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(new_bit) {
|
||||
instance->raw_data[byte_idx] |= (1U << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->bit_count < 64) {
|
||||
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||
} else {
|
||||
instance->extra_data = (instance->extra_data << 1) | new_bit;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
frame_complete = true;
|
||||
}
|
||||
}
|
||||
} else if(instance->bit_count >= (FIAT_MARELLI_MAX_DATA_BITS - 2)) {
|
||||
frame_complete = true;
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "invalid manchester timing");
|
||||
}
|
||||
|
||||
if(frame_complete) {
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
if(!fiat_marelli_try_recover_tail_bits(instance)) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
|
||||
bool crc_ok = true;
|
||||
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_ok = (calc == instance->raw_data[12]);
|
||||
}
|
||||
|
||||
if(crc_ok) {
|
||||
FURI_LOG_D(TAG, "Frame accepted (%u bits, CRC OK)", instance->bit_count);
|
||||
instance->generic.serial = ((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
|
||||
}
|
||||
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit =
|
||||
instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
|
||||
};
|
||||
uint8_t hash =
|
||||
subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
uint32_t x = instance->extra_data;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
hash ^= (uint8_t)(x >> (i * 8));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
||||
|
||||
uint32_t extra_bits =
|
||||
(instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0;
|
||||
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
||||
|
||||
uint32_t te = instance->te_detected;
|
||||
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
||||
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_fiat_marelli_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(instance->generic.data_count_bit != FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->extra_data = extra;
|
||||
}
|
||||
|
||||
uint32_t te = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||
instance->te_detected = te;
|
||||
}
|
||||
|
||||
fiat_marelli_rebuild_raw_data(instance);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||
|
||||
uint8_t epoch = instance->raw_data[6] & 0x0F;
|
||||
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x03;
|
||||
uint8_t fixed = instance->raw_data[7] & 0x01;
|
||||
const char* crc_state = "N/A";
|
||||
uint8_t crc_value = instance->raw_data[12];
|
||||
|
||||
if(instance->bit_count >= 104) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_state = (calc == instance->raw_data[12]) ? "OK" : "FAIL";
|
||||
}
|
||||
|
||||
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"
|
||||
"CRC:%02X [%s]\r\n",
|
||||
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,
|
||||
fiat_marelli_button_name(instance->generic.btn),
|
||||
(unsigned)epoch,
|
||||
(unsigned)crc_value,
|
||||
crc_state);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatMarelli SubGhzProtocolDecoderFiatMarelli;
|
||||
|
||||
extern const SubGhzProtocol fiat_v1_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_marelli_free(void* context);
|
||||
void subghz_protocol_decoder_fiat_marelli_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,837 @@
|
||||
#include "ford_v0.h"
|
||||
#include "protocols_common.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
|
||||
#define TAG "FordProtocolV0"
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
// Uncomment to enable bit-level debug logging (WARNING: 80 log calls per signal)
|
||||
// #define FORD_V0_DEBUG_BITS
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define FORD_V0_PREAMBLE_PAIRS 4
|
||||
#define FORD_V0_GAP_US 3500
|
||||
#define FORD_V0_TOTAL_BURSTS 6
|
||||
#define FORD_V0_UPLOAD_CAPACITY (((FORD_V0_TOTAL_BURSTS - 1U) * 169U) + 168U)
|
||||
_Static_assert(
|
||||
FORD_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"FORD_V0_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
// =============================================================================
|
||||
// CRC MATRIX
|
||||
// Ford V0 uses matrix multiplication in GF(2) for CRC calculation
|
||||
// =============================================================================
|
||||
|
||||
static const uint8_t ford_v0_crc_matrix[64] = {
|
||||
0xDA, 0xB5, 0x55, 0x6A, 0xAA, 0xAA, 0xAA, 0xD5, 0xB6, 0x6C, 0xCC, 0xD9, 0x99, 0x99, 0x99, 0xB3,
|
||||
0x71, 0xE3, 0xC3, 0xC7, 0x87, 0x87, 0x87, 0x8F, 0x0F, 0xE0, 0x3F, 0xC0, 0x7F, 0x80, 0x7F, 0x80,
|
||||
0x00, 0x1F, 0xFF, 0xC0, 0x00, 0x7F, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0x80,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x23, 0x12, 0x94, 0x84, 0x35, 0xF4, 0x55, 0x84,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// STRUCT DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
|
||||
uint64_t data_low;
|
||||
uint64_t data_high;
|
||||
uint8_t bit_count;
|
||||
|
||||
uint16_t header_count;
|
||||
|
||||
uint64_t key1;
|
||||
uint16_t key2;
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolDecoderFordV0;
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderFordV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t key1;
|
||||
uint16_t key2;
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
uint8_t checksum;
|
||||
} SubGhzProtocolEncoderFordV0;
|
||||
#endif
|
||||
typedef enum {
|
||||
FordV0DecoderStepReset = 0,
|
||||
FordV0DecoderStepPreamble,
|
||||
FordV0DecoderStepPreambleCheck,
|
||||
FordV0DecoderStepGap,
|
||||
FordV0DecoderStepData,
|
||||
} FordV0DecoderStep;
|
||||
|
||||
// =============================================================================
|
||||
// FUNCTION PROTOTYPES
|
||||
// =============================================================================
|
||||
|
||||
static void ford_v0_add_bit(SubGhzProtocolDecoderFordV0* instance, bool bit);
|
||||
static void decode_ford_v0(
|
||||
uint64_t key1,
|
||||
uint16_t key2,
|
||||
uint32_t* serial,
|
||||
uint8_t* button,
|
||||
uint32_t* count);
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void encode_ford_v0(
|
||||
uint8_t header_byte,
|
||||
uint32_t serial,
|
||||
uint8_t button,
|
||||
uint32_t count,
|
||||
uint8_t checksum,
|
||||
uint64_t* key1);
|
||||
#endif
|
||||
static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance);
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v0_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_ford_v0_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v0_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = subghz_protocol_decoder_ford_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v0_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v0_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v0_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol ford_protocol_v0 = {
|
||||
.name = FORD_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_ford_v0_decoder,
|
||||
.encoder = &subghz_protocol_ford_v0_encoder,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// CHECKSUM CALCULATION
|
||||
// =============================================================================
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint8_t ford_v0_calculate_checksum(uint32_t serial, uint32_t count, uint8_t button) {
|
||||
return (uint8_t)((((count >> 24) & 0xFF) + ((count >> 16) & 0xFF) + ((count >> 8) & 0xFF) +
|
||||
(count & 0xFF) + ((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) +
|
||||
((serial >> 8) & 0xFF) + (serial & 0xFF) + (button << 3)) &
|
||||
0xFF);
|
||||
}
|
||||
#endif
|
||||
// =============================================================================
|
||||
// CRC FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
static uint8_t ford_v0_calculate_crc(uint8_t* buf) {
|
||||
uint8_t crc = 0;
|
||||
|
||||
for(int row = 0; row < 8; row++) {
|
||||
uint8_t xor_sum = 0;
|
||||
for(int col = 0; col < 8; col++) {
|
||||
xor_sum ^= (ford_v0_crc_matrix[row * 8 + col] & buf[col + 1]);
|
||||
}
|
||||
uint8_t parity = subghz_protocol_blocks_parity8(xor_sum);
|
||||
if(parity) {
|
||||
crc |= (1 << row);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint8_t ford_v0_calculate_crc_for_tx(uint64_t key1, uint8_t checksum) {
|
||||
uint8_t buf[16] = {0};
|
||||
|
||||
for(int i = 0; i < 8; ++i) {
|
||||
buf[i] = (uint8_t)(key1 >> (56 - i * 8));
|
||||
}
|
||||
|
||||
buf[8] = checksum;
|
||||
|
||||
uint8_t crc = ford_v0_calculate_crc(buf);
|
||||
return crc ^ 0x80;
|
||||
}
|
||||
#endif
|
||||
static bool ford_v0_verify_crc(uint64_t key1, uint16_t key2) {
|
||||
uint8_t buf[16] = {0};
|
||||
|
||||
for(int i = 0; i < 8; ++i) {
|
||||
buf[i] = (uint8_t)(key1 >> (56 - i * 8));
|
||||
}
|
||||
|
||||
buf[8] = (uint8_t)(key2 >> 8);
|
||||
|
||||
uint8_t calculated_crc = ford_v0_calculate_crc(buf);
|
||||
uint8_t received_crc = (uint8_t)(key2 & 0xFF) ^ 0x80;
|
||||
|
||||
return ((calculated_crc & 0x7F) == (received_crc & 0x7F));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// DECODE FUNCTION
|
||||
// =============================================================================
|
||||
|
||||
static void decode_ford_v0(
|
||||
uint64_t key1,
|
||||
uint16_t key2,
|
||||
uint32_t* serial,
|
||||
uint8_t* button,
|
||||
uint32_t* count) {
|
||||
uint8_t buf[13] = {0};
|
||||
|
||||
for(int i = 0; i < 8; ++i) {
|
||||
buf[i] = (uint8_t)(key1 >> (56 - i * 8));
|
||||
}
|
||||
|
||||
buf[8] = (uint8_t)(key2 >> 8);
|
||||
buf[9] = (uint8_t)(key2 & 0xFF);
|
||||
|
||||
uint8_t tmp = buf[8];
|
||||
uint8_t parity = 0;
|
||||
uint8_t parity_any = (tmp != 0);
|
||||
while(tmp) {
|
||||
parity ^= (tmp & 1);
|
||||
tmp >>= 1;
|
||||
}
|
||||
buf[11] = parity_any ? parity : 0;
|
||||
|
||||
uint8_t xor_byte;
|
||||
uint8_t limit;
|
||||
if(buf[11]) {
|
||||
xor_byte = buf[7];
|
||||
limit = 7;
|
||||
} else {
|
||||
xor_byte = buf[6];
|
||||
limit = 6;
|
||||
}
|
||||
|
||||
for(int idx = 1; idx < limit; ++idx) {
|
||||
buf[idx] ^= xor_byte;
|
||||
}
|
||||
|
||||
if(buf[11] == 0) {
|
||||
buf[7] ^= xor_byte;
|
||||
}
|
||||
|
||||
uint8_t orig_b7 = buf[7];
|
||||
buf[7] = (orig_b7 & 0xAA) | (buf[6] & 0x55);
|
||||
uint8_t mixed = (buf[6] & 0xAA) | (orig_b7 & 0x55);
|
||||
buf[12] = mixed;
|
||||
buf[6] = mixed;
|
||||
|
||||
uint32_t serial_le = ((uint32_t)buf[1]) | ((uint32_t)buf[2] << 8) | ((uint32_t)buf[3] << 16) |
|
||||
((uint32_t)buf[4] << 24);
|
||||
|
||||
*serial = ((serial_le & 0xFF) << 24) | (((serial_le >> 8) & 0xFF) << 16) |
|
||||
(((serial_le >> 16) & 0xFF) << 8) | ((serial_le >> 24) & 0xFF);
|
||||
|
||||
*button = (buf[5] >> 3) & 0x0F;
|
||||
|
||||
*count = ((buf[5] & 0x07) << 16) | (buf[6] << 8) | buf[7];
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENCODE FUNCTION
|
||||
// =============================================================================
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void encode_ford_v0(
|
||||
uint8_t header_byte,
|
||||
uint32_t serial,
|
||||
uint8_t button,
|
||||
uint32_t count,
|
||||
uint8_t checksum,
|
||||
uint64_t* key1) {
|
||||
if(!key1) {
|
||||
FURI_LOG_E(TAG, "encode_ford_v0: NULL key1 pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t buf[8] = {0};
|
||||
|
||||
buf[0] = header_byte;
|
||||
|
||||
buf[1] = (serial >> 24) & 0xFF;
|
||||
buf[2] = (serial >> 16) & 0xFF;
|
||||
buf[3] = (serial >> 8) & 0xFF;
|
||||
buf[4] = serial & 0xFF;
|
||||
|
||||
buf[5] = ((button & 0x0F) << 3) | ((count >> 16) & 0x0F);
|
||||
|
||||
uint8_t count_mid = (count >> 8) & 0xFF;
|
||||
uint8_t count_low = count & 0xFF;
|
||||
|
||||
uint8_t post_xor_6 = (count_mid & 0xAA) | (count_low & 0x55);
|
||||
uint8_t post_xor_7 = (count_low & 0xAA) | (count_mid & 0x55);
|
||||
|
||||
uint8_t parity = 0;
|
||||
uint8_t tmp = checksum;
|
||||
while(tmp) {
|
||||
parity ^= (tmp & 1);
|
||||
tmp >>= 1;
|
||||
}
|
||||
bool parity_bit = (checksum != 0) ? (parity != 0) : false;
|
||||
|
||||
if(parity_bit) {
|
||||
uint8_t xor_byte = post_xor_7;
|
||||
buf[1] ^= xor_byte;
|
||||
buf[2] ^= xor_byte;
|
||||
buf[3] ^= xor_byte;
|
||||
buf[4] ^= xor_byte;
|
||||
buf[5] ^= xor_byte;
|
||||
buf[6] = post_xor_6 ^ xor_byte;
|
||||
buf[7] = post_xor_7;
|
||||
} else {
|
||||
uint8_t xor_byte = post_xor_6;
|
||||
buf[1] ^= xor_byte;
|
||||
buf[2] ^= xor_byte;
|
||||
buf[3] ^= xor_byte;
|
||||
buf[4] ^= xor_byte;
|
||||
buf[5] ^= xor_byte;
|
||||
buf[6] = post_xor_6;
|
||||
buf[7] = post_xor_7 ^ xor_byte;
|
||||
}
|
||||
|
||||
*key1 = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
*key1 = (*key1 << 8) | buf[i];
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encode: Sn=%08lX Btn=%d Cnt=%05lX Checksum=%02X",
|
||||
(unsigned long)serial,
|
||||
button,
|
||||
(unsigned long)count,
|
||||
checksum);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encode key1: %08lX%08lX",
|
||||
(unsigned long)(*key1 >> 32),
|
||||
(unsigned long)(*key1 & 0xFFFFFFFF));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// =============================================================================
|
||||
|
||||
void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV0* instance = malloc(sizeof(SubGhzProtocolEncoderFordV0));
|
||||
|
||||
instance->base.protocol = &ford_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
pp_encoder_buffer_ensure(instance, FORD_V0_UPLOAD_CAPACITY);
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
instance->key1 = 0;
|
||||
instance->key2 = 0;
|
||||
instance->serial = 0;
|
||||
instance->button = 0;
|
||||
instance->count = 0;
|
||||
instance->checksum = 0;
|
||||
|
||||
FURI_LOG_I(TAG, "Encoder allocated");
|
||||
return instance;
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_ford_v0_get_upload(SubGhzProtocolEncoderFordV0* instance) {
|
||||
furi_check(instance);
|
||||
size_t index = 0;
|
||||
|
||||
uint64_t tx_key1 = ~instance->key1;
|
||||
uint16_t tx_key2 = ~instance->key2;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Building upload: key1=%08lX%08lX key2=%04X",
|
||||
(unsigned long)(instance->key1 >> 32),
|
||||
(unsigned long)(instance->key1 & 0xFFFFFFFF),
|
||||
instance->key2);
|
||||
|
||||
uint32_t te_short = subghz_protocol_ford_v0_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_ford_v0_const.te_long;
|
||||
|
||||
#define ADD_LEVEL(lvl, dur) \
|
||||
index = pp_emit_merge(instance->encoder.upload, index, FORD_V0_UPLOAD_CAPACITY, (lvl), (dur))
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V0_TOTAL_BURSTS; burst++) {
|
||||
ADD_LEVEL(true, te_short);
|
||||
ADD_LEVEL(false, te_long);
|
||||
|
||||
for(int i = 0; i < FORD_V0_PREAMBLE_PAIRS; i++) {
|
||||
ADD_LEVEL(true, te_long);
|
||||
ADD_LEVEL(false, te_long);
|
||||
}
|
||||
|
||||
ADD_LEVEL(true, te_short);
|
||||
ADD_LEVEL(false, FORD_V0_GAP_US);
|
||||
|
||||
bool first_bit = (tx_key1 >> 62) & 1;
|
||||
if(first_bit) {
|
||||
ADD_LEVEL(true, te_long);
|
||||
} else {
|
||||
ADD_LEVEL(true, te_short);
|
||||
ADD_LEVEL(false, te_long);
|
||||
}
|
||||
|
||||
bool prev_bit = first_bit;
|
||||
|
||||
for(int bit = 61; bit >= 0; bit--) {
|
||||
bool curr_bit = (tx_key1 >> bit) & 1;
|
||||
|
||||
if(!prev_bit && !curr_bit) {
|
||||
ADD_LEVEL(true, te_short);
|
||||
ADD_LEVEL(false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
ADD_LEVEL(true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
ADD_LEVEL(false, te_long);
|
||||
} else {
|
||||
ADD_LEVEL(false, te_short);
|
||||
ADD_LEVEL(true, te_short);
|
||||
}
|
||||
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
for(int bit = 15; bit >= 0; bit--) {
|
||||
bool curr_bit = (tx_key2 >> bit) & 1;
|
||||
|
||||
if(!prev_bit && !curr_bit) {
|
||||
ADD_LEVEL(true, te_short);
|
||||
ADD_LEVEL(false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
ADD_LEVEL(true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
ADD_LEVEL(false, te_long);
|
||||
} else {
|
||||
ADD_LEVEL(false, te_short);
|
||||
ADD_LEVEL(true, te_short);
|
||||
}
|
||||
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
if(burst < FORD_V0_TOTAL_BURSTS - 1) {
|
||||
ADD_LEVEL(false, te_long * 100);
|
||||
}
|
||||
}
|
||||
|
||||
#undef ADD_LEVEL
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(TAG, "Upload built: %d bursts, size=%zu", FORD_V0_TOTAL_BURSTS, index);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
do {
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t original_key1 = 0;
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &original_key1)) {
|
||||
break;
|
||||
}
|
||||
uint8_t header_byte = (uint8_t)(original_key1 >> 56);
|
||||
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) break;
|
||||
|
||||
instance->serial = serial;
|
||||
instance->button = (uint8_t)btn;
|
||||
instance->count = cnt;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
// Calculate Checksum from counter and button.
|
||||
instance->checksum =
|
||||
ford_v0_calculate_checksum(instance->serial, instance->count, instance->button);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Calculated Checksum: 0x%02X (from Cnt=0x%05lX, Btn=0x%02X)",
|
||||
instance->checksum,
|
||||
(unsigned long)instance->count,
|
||||
instance->button);
|
||||
|
||||
encode_ford_v0(
|
||||
header_byte,
|
||||
instance->serial,
|
||||
instance->button,
|
||||
instance->count,
|
||||
instance->checksum,
|
||||
&instance->key1);
|
||||
|
||||
instance->generic.data = instance->key1;
|
||||
instance->generic.data_count_bit = 64;
|
||||
|
||||
uint8_t calculated_crc = ford_v0_calculate_crc_for_tx(instance->key1, instance->checksum);
|
||||
instance->key2 = ((uint16_t)instance->checksum << 8) | calculated_crc;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Final key2: 0x%04X (Checksum=0x%02X, CRC=0x%02X)",
|
||||
instance->key2,
|
||||
instance->checksum,
|
||||
calculated_crc);
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
subghz_protocol_encoder_ford_v0_get_upload(instance);
|
||||
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
FURI_LOG_E(TAG, "Upload build failed");
|
||||
break;
|
||||
}
|
||||
|
||||
//Update the PSF file, since we have overwritten the COUNTER and BUTTON
|
||||
//This makes the file's nummers wrong, and fails tests. It wasnt causing a TX bug, but manual tests failed.
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t temp = calculated_crc;
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "CRC", &temp, 1);
|
||||
temp = instance->checksum;
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &temp, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder ready: size=%zu repeat=%u",
|
||||
instance->encoder.size_upload,
|
||||
instance->encoder.repeat);
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
FURI_LOG_I(TAG, "Encoder deserialize finished, status=%d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
// =============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// =============================================================================
|
||||
|
||||
static void ford_v0_add_bit(SubGhzProtocolDecoderFordV0* instance, bool bit) {
|
||||
#ifdef FORD_V0_DEBUG_BITS
|
||||
FURI_LOG_D(TAG, "Bit %d: %d", instance->bit_count, bit);
|
||||
#endif
|
||||
|
||||
uint32_t low = (uint32_t)instance->data_low;
|
||||
instance->data_low = (instance->data_low << 1) | (bit ? 1 : 0);
|
||||
instance->data_high = (instance->data_high << 1) | ((low >> 31) & 1);
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance) {
|
||||
if(instance->bit_count == 64) {
|
||||
uint64_t combined = ((uint64_t)instance->data_high << 32) | instance->data_low;
|
||||
instance->key1 = ~combined;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(instance->bit_count == 80) {
|
||||
uint16_t key2_raw = (uint16_t)(instance->data_low & 0xFFFF);
|
||||
uint16_t key2 = ~key2_raw;
|
||||
|
||||
decode_ford_v0(
|
||||
instance->key1, key2, &instance->serial, &instance->button, &instance->count);
|
||||
|
||||
instance->key2 = key2;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFordV0* instance = malloc(sizeof(SubGhzProtocolDecoderFordV0));
|
||||
instance->base.protocol = &ford_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV0* instance = context;
|
||||
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->header_count = 0;
|
||||
instance->key1 = 0;
|
||||
instance->key2 = 0;
|
||||
instance->serial = 0;
|
||||
instance->button = 0;
|
||||
instance->count = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV0* instance = context;
|
||||
|
||||
uint32_t te_short = subghz_protocol_ford_v0_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_ford_v0_const.te_long;
|
||||
uint32_t te_delta = subghz_protocol_ford_v0_const.te_delta;
|
||||
uint32_t gap_threshold = 3500;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV0DecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, te_short) < te_delta)) {
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->decoder.parser_step = FordV0DecoderStepPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->bit_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV0DecoderStepPreamble:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = FordV0DecoderStepPreambleCheck;
|
||||
} else {
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV0DecoderStepPreambleCheck:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
instance->header_count++;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = FordV0DecoderStepPreamble;
|
||||
} else if(DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
instance->decoder.parser_step = FordV0DecoderStepGap;
|
||||
} else {
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV0DecoderStepGap:
|
||||
if(!level && (DURATION_DIFF(duration, gap_threshold) < 250)) {
|
||||
instance->data_low = 1;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 1;
|
||||
instance->decoder.parser_step = FordV0DecoderStepData;
|
||||
} else if(!level && duration > gap_threshold + 250) {
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV0DecoderStepData: {
|
||||
ManchesterEvent event =
|
||||
pp_manchester_event(duration, level, &subghz_protocol_ford_v0_const);
|
||||
if(event == ManchesterEventReset) {
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
bool data_bit;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
ford_v0_add_bit(instance, data_bit);
|
||||
|
||||
if(ford_v0_process_data(instance)) {
|
||||
instance->generic.data = instance->key1;
|
||||
instance->generic.data_count_bit = 64;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = FordV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = (instance->key2 >> 8) & 0xFF;
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
|
||||
|
||||
temp = instance->key2 & 0xFF;
|
||||
flipper_format_write_uint32(flipper_format, "CRC", &temp, 1);
|
||||
|
||||
pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->serial,
|
||||
instance->button,
|
||||
instance->count,
|
||||
0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_ford_v0_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
instance->key1 = instance->generic.data;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
uint32_t checksum_temp = 0;
|
||||
uint32_t crc_temp = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Checksum", &checksum_temp, 1);
|
||||
flipper_format_read_uint32(flipper_format, "CRC", &crc_temp, 1);
|
||||
instance->key2 = ((checksum_temp & 0xFF) << 8) | (crc_temp & 0xFF);
|
||||
|
||||
uint32_t serial = instance->serial;
|
||||
uint32_t btn = instance->button;
|
||||
uint32_t cnt = instance->count;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
instance->serial = serial;
|
||||
instance->button = (uint8_t)btn;
|
||||
instance->count = cnt;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV0* instance = context;
|
||||
|
||||
uint32_t code_found_hi = (uint32_t)(instance->key1 >> 32);
|
||||
uint32_t code_found_lo = (uint32_t)(instance->key1 & 0xFFFFFFFF);
|
||||
|
||||
bool crc_ok = ford_v0_verify_crc(instance->key1, instance->key2);
|
||||
|
||||
const char* button_name = "??";
|
||||
if(instance->button == 0x01)
|
||||
button_name = "Panic";
|
||||
else if(instance->button == 0x02)
|
||||
button_name = "Lock";
|
||||
else if(instance->button == 0x04)
|
||||
button_name = "Unlock";
|
||||
else if(instance->button == 0x08)
|
||||
button_name = "Boot";
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit CRC:%s\r\n"
|
||||
"Key1: %08lX%08lX\r\n"
|
||||
"Key2: %04X"
|
||||
" Sn: %08lX\r\n"
|
||||
"Cnt: %05lX"
|
||||
" Checksum: %02X"
|
||||
" CRC: %02X\r\n"
|
||||
" Btn: %02X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
crc_ok ? "OK" : "BAD",
|
||||
(unsigned long)code_found_hi,
|
||||
(unsigned long)code_found_lo,
|
||||
instance->key2,
|
||||
(unsigned long)instance->serial,
|
||||
|
||||
(unsigned long)instance->count,
|
||||
(instance->key2 >> 8) & 0xFF,
|
||||
instance->key2 & 0xFF,
|
||||
instance->button,
|
||||
button_name);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#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>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define FORD_PROTOCOL_V0_NAME "Ford V0"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v0;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_ford_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v0_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v0_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -0,0 +1,40 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define FORD_PROTOCOL_V1_NAME "Ford V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV1 SubGhzProtocolDecoderFordV1;
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v1;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v1_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v1_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder;
|
||||
#endif
|
||||
@@ -0,0 +1,829 @@
|
||||
#include "ford_v2.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
|
||||
#define FORD_V2_TE_SHORT 200U
|
||||
#define FORD_V2_TE_LONG 400U
|
||||
#define FORD_V2_TE_DELTA 260U
|
||||
#define FORD_V2_INTER_BURST_GAP_US 15000U
|
||||
#define FORD_V2_PREAMBLE_MIN 64U
|
||||
#define FORD_V2_DATA_BITS 104U
|
||||
#define FORD_V2_DATA_BYTES 13U
|
||||
#define FORD_V2_SYNC_0 0x7FU
|
||||
#define FORD_V2_SYNC_1 0xA7U
|
||||
#define FORD_V2_ENC_TE_SHORT 240U
|
||||
#define FORD_V2_ENC_PREAMBLE_PAIRS 70U
|
||||
#define FORD_V2_ENC_BURST_COUNT 6U
|
||||
#define FORD_V2_ENC_INTER_BURST_GAP_US 16000U
|
||||
#define FORD_V2_ENC_ALLOC_ELEMS 2600U
|
||||
#define FORD_V2_ENC_SEPARATOR_ELEMS 2U
|
||||
#define FORD_V2_ENC_PREAMBLE_ELEMS (FORD_V2_ENC_PREAMBLE_PAIRS * 2U)
|
||||
#define FORD_V2_ENC_DATA_ELEMS ((FORD_V2_DATA_BITS - 1U) * 2U)
|
||||
#define FORD_V2_ENC_BURST_ELEMS \
|
||||
(FORD_V2_ENC_PREAMBLE_ELEMS + FORD_V2_ENC_SEPARATOR_ELEMS + FORD_V2_ENC_DATA_ELEMS)
|
||||
#define FORD_V2_ENC_UPLOAD_ELEMS \
|
||||
(FORD_V2_ENC_BURST_COUNT * FORD_V2_ENC_BURST_ELEMS + (FORD_V2_ENC_BURST_COUNT - 1U))
|
||||
#define FORD_V2_ENC_SYNC_LO_US 476U
|
||||
|
||||
#define FORD_V2_SYNC_BITS 16U
|
||||
#define FORD_V2_POST_SYNC_DECODE_COUNT_BIT 16U
|
||||
#define FORD_V2_KEY_BYTE_COUNT 8U
|
||||
#define FORD_V2_TAIL_RAW_BYTE_COUNT 5U
|
||||
#define FORD_V2_PREAMBLE_COUNT_MAX 0xFFFFU
|
||||
#define FORD_V2_ENCODER_DEFAULT_REPEAT 10U
|
||||
|
||||
static const uint16_t ford_v2_sync_shift16_inv =
|
||||
(uint16_t)(~(((uint16_t)FORD_V2_SYNC_0 << 8) | (uint16_t)FORD_V2_SYNC_1));
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v2_const = {
|
||||
.te_short = FORD_V2_TE_SHORT,
|
||||
.te_long = FORD_V2_TE_LONG,
|
||||
.te_delta = FORD_V2_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V2_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV2DecoderStepReset = 0,
|
||||
FordV2DecoderStepPreamble = 1,
|
||||
FordV2DecoderStepSync = 2,
|
||||
FordV2DecoderStepData = 3,
|
||||
} FordV2DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
uint8_t byte_count;
|
||||
|
||||
uint16_t sync_shift;
|
||||
uint8_t sync_bit_count;
|
||||
|
||||
uint64_t extra_data;
|
||||
uint16_t counter16;
|
||||
uint32_t tail31;
|
||||
bool structure_ok;
|
||||
} SubGhzProtocolDecoderFordV2;
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderFordV2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint64_t extra_data;
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
} SubGhzProtocolEncoderFordV2;
|
||||
#endif
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event);
|
||||
|
||||
static void ford_v2_decoder_reset_state(SubGhzProtocolDecoderFordV2* instance) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->counter16 = 0;
|
||||
instance->tail31 = 0;
|
||||
instance->structure_ok = false;
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_SHORT) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_LONG) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_button_is_valid(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint8_t ford_v2_uint8_parity(uint8_t value) {
|
||||
uint8_t parity = 0U;
|
||||
while(value) {
|
||||
parity ^= (value & 1U);
|
||||
value >>= 1U;
|
||||
}
|
||||
return parity;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char* ford_v2_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
return "Lock";
|
||||
case 0x11:
|
||||
return "Unlock";
|
||||
case 0x13:
|
||||
return "Trunk";
|
||||
case 0x14:
|
||||
return "Panic";
|
||||
case 0x15:
|
||||
return "RemoteStart";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_extract_from_raw(SubGhzProtocolDecoderFordV2* instance) {
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
instance->generic.serial = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) |
|
||||
((uint32_t)k[4] << 8) | (uint32_t)k[5];
|
||||
|
||||
instance->generic.btn = k[6];
|
||||
|
||||
instance->counter16 = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) | (((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
|
||||
instance->generic.cnt = instance->counter16;
|
||||
|
||||
instance->tail31 = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
instance->structure_ok = true;
|
||||
|
||||
if(k[0] != FORD_V2_SYNC_0) instance->structure_ok = false;
|
||||
if(k[1] != FORD_V2_SYNC_1) instance->structure_ok = false;
|
||||
if(!ford_v2_button_is_valid(k[6])) instance->structure_ok = false;
|
||||
|
||||
if((k[7] & 0x7FU) != (uint8_t)((instance->counter16 >> 9) & 0x7FU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(k[8] != (uint8_t)((instance->counter16 >> 1) & 0xFFU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(((k[9] >> 7) & 1U) != (uint8_t)(instance->counter16 & 1U)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)k[i];
|
||||
}
|
||||
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
instance->extra_data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)k[8U + i];
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_commit_frame(SubGhzProtocolDecoderFordV2* instance) {
|
||||
if(instance->raw_bytes[0] != FORD_V2_SYNC_0 || instance->raw_bytes[1] != FORD_V2_SYNC_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_sync_enter_data(SubGhzProtocolDecoderFordV2* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->raw_bytes[0] = FORD_V2_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V2_SYNC_1;
|
||||
instance->byte_count = 2U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepData;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = FORD_V2_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool
|
||||
ford_v2_decoder_sync_feed_event(SubGhzProtocolDecoderFordV2* instance, ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->sync_shift = (uint16_t)((instance->sync_shift << 1) | (data_bit ? 1U : 0U));
|
||||
if(instance->sync_bit_count < FORD_V2_SYNC_BITS) {
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return instance->sync_bit_count >= FORD_V2_SYNC_BITS &&
|
||||
instance->sync_shift == ford_v2_sync_shift16_inv;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
if(ford_v2_decoder_sync_feed_event(instance, event)) {
|
||||
ford_v2_decoder_sync_enter_data(instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.parser_step != FordV2DecoderStepData) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if((instance->decoder.decode_count_bit & 7U) == 0U) {
|
||||
uint8_t byte_val = (uint8_t)(instance->decoder.decode_data & 0xFFU);
|
||||
|
||||
if(instance->byte_count < FORD_V2_DATA_BYTES) {
|
||||
instance->raw_bytes[instance->byte_count] = byte_val;
|
||||
instance->byte_count++;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
|
||||
if(instance->byte_count == FORD_V2_DATA_BYTES) {
|
||||
(void)ford_v2_decoder_commit_frame(instance);
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_manchester_feed_pulse(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_enter_sync_from_preamble(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepSync;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_rebuild_raw_buffer(SubGhzProtocolDecoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static inline void ford_v2_encoder_add_level(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t idx = instance->encoder.size_upload;
|
||||
if(idx > 0 && level_duration_get_level(instance->encoder.upload[idx - 1]) == level) {
|
||||
uint32_t prev = level_duration_get_duration(instance->encoder.upload[idx - 1]);
|
||||
instance->encoder.upload[idx - 1] = level_duration_make(level, prev + duration);
|
||||
} else {
|
||||
furi_check(idx < FORD_V2_ENC_ALLOC_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_rebuild_raw_from_payload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
|
||||
const uint8_t btn = instance->raw_bytes[6];
|
||||
const uint8_t k7_msb = (uint8_t)(ford_v2_uint8_parity(btn) << 7);
|
||||
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x7FU) | k7_msb;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_refresh_data_from_raw(SubGhzProtocolEncoderFordV2* instance) {
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)instance->raw_bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
ford_v2_encoder_emit_manchester_bit(SubGhzProtocolEncoderFordV2* instance, bool bit) {
|
||||
if(bit) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
} else {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_emit_burst(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_ENC_PREAMBLE_PAIRS; i++) {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_SYNC_LO_US);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
|
||||
for(uint16_t bit_pos = 1U; bit_pos < FORD_V2_DATA_BITS; bit_pos++) {
|
||||
const uint8_t byte_idx = (uint8_t)(bit_pos / 8U);
|
||||
const uint8_t bit_idx = (uint8_t)(7U - (bit_pos % 8U));
|
||||
ford_v2_encoder_emit_manchester_bit(
|
||||
instance, ((instance->raw_bytes[byte_idx] >> bit_idx) & 1U) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_build_upload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V2_ENC_BURST_COUNT; burst++) {
|
||||
ford_v2_encoder_emit_burst(instance);
|
||||
|
||||
if(burst + 1U < FORD_V2_ENC_BURST_COUNT) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_read_optional_tail_raw(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
instance->extra_data = 0U;
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus ford_v2_encoder_deserialize_read_header(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* temp_str) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus g = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, FORD_V2_DATA_BITS);
|
||||
if(g != SubGhzProtocolStatusOk) {
|
||||
return g;
|
||||
}
|
||||
|
||||
ford_v2_encoder_read_optional_tail_raw(instance, flipper_format);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
ford_v2_encoder_deserialize_validate_and_pack(SubGhzProtocolEncoderFordV2* instance) {
|
||||
ford_v2_encoder_rebuild_raw_from_payload(instance);
|
||||
|
||||
if(!ford_v2_button_is_valid(instance->raw_bytes[6])) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
ford_v2_encoder_refresh_data_from_raw(instance);
|
||||
instance->generic.btn = instance->raw_bytes[6];
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_bytes[2] << 24) | ((uint32_t)instance->raw_bytes[3] << 16) |
|
||||
((uint32_t)instance->raw_bytes[4] << 8) | (uint32_t)instance->raw_bytes[5];
|
||||
instance->generic.cnt = (uint16_t)((((uint16_t)(instance->raw_bytes[7] & 0x7FU)) << 9) |
|
||||
(((uint16_t)instance->raw_bytes[8]) << 1) |
|
||||
((uint16_t)(instance->raw_bytes[9] >> 7)));
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_deserialize_apply_repeat(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
instance->encoder.repeat = repeat;
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->encoder.upload = calloc(FORD_V2_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
ford_v2_encoder_deserialize_read_header(instance, flipper_format, temp_str);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
ret = ford_v2_encoder_deserialize_validate_and_pack(instance);
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
ford_v2_encoder_deserialize_apply_repeat(instance, flipper_format);
|
||||
ford_v2_encoder_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0U) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
ford_v2_decoder_reset_state((SubGhzProtocolDecoderFordV2*)context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV2DecoderStepReset:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepPreamble:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
if(instance->preamble_count < FORD_V2_PREAMBLE_COUNT_MAX) {
|
||||
instance->preamble_count++;
|
||||
}
|
||||
} else if(!level && ford_v2_duration_is_long(duration)) {
|
||||
if(instance->preamble_count >= FORD_V2_PREAMBLE_MIN) {
|
||||
ford_v2_decoder_enter_sync_from_preamble(instance, level, duration);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepSync:
|
||||
case FordV2DecoderStepData:
|
||||
if(ford_v2_decoder_manchester_feed_pulse(instance, level, duration)) {
|
||||
} else {
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync &&
|
||||
duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepData) {
|
||||
if(duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
const uint16_t cnt = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) | (((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
const uint32_t tail = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
uint32_t mix = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) | ((uint32_t)k[4] << 8) |
|
||||
(uint32_t)k[5];
|
||||
mix ^= (uint32_t)k[6] << 16;
|
||||
mix ^= (uint32_t)cnt << 8;
|
||||
mix ^= tail;
|
||||
|
||||
return (uint8_t)((mix >> 0) ^ (mix >> 8) ^ (mix >> 16) ^ (mix >> 24) ^ (uint8_t)(cnt >> 8) ^
|
||||
(uint8_t)(tail >> 16));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &btn, 1);
|
||||
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
uint32_t tail31 = instance->tail31;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Tail31", &tail31, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, "TailRaw", &instance->raw_bytes[8], 5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_read_tail_raw_if_present(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
instance->extra_data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_ford_v2_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if(instance->generic.data_count_bit != FORD_V2_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
ford_v2_decoder_read_tail_raw_if_present(instance, flipper_format);
|
||||
|
||||
ford_v2_decoder_rebuild_raw_buffer(instance);
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%08lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%u Struct:%s\r\n"
|
||||
"Tail31:%08lX\r\n"
|
||||
"TailRaw:%02X%02X%02X%02X%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[2],
|
||||
k[3],
|
||||
k[4],
|
||||
k[5],
|
||||
k[6],
|
||||
k[7],
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v2_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter16,
|
||||
instance->structure_ok ? "OK" : "BAD",
|
||||
(unsigned long)instance->tail31,
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12]);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v2_free,
|
||||
.feed = subghz_protocol_decoder_ford_v2_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v2_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v2_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v2_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v2_deserialize,
|
||||
.stop = subghz_protocol_encoder_ford_v2_stop,
|
||||
.yield = subghz_protocol_encoder_ford_v2_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol ford_protocol_v2 = {
|
||||
.name = FORD_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
| SubGhzProtocolFlag_Send
|
||||
#endif
|
||||
,
|
||||
.decoder = &subghz_protocol_ford_v2_decoder,
|
||||
.encoder = &subghz_protocol_ford_v2_encoder,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define FORD_PROTOCOL_V2_NAME "Ford V2"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v2;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder;
|
||||
#endif
|
||||
@@ -0,0 +1,343 @@
|
||||
#include "ford_v3.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.h>
|
||||
|
||||
#define FORD_V3_TE_SHORT 240U
|
||||
#define FORD_V3_TE_LONG 480U
|
||||
#define FORD_V3_TE_DELTA 60U
|
||||
#define FORD_V3_DATA_BITS 104U
|
||||
#define FORD_V3_DATA_BYTES 13U
|
||||
#define FORD_V3_PREAMBLE_MIN 30U
|
||||
|
||||
#define FORD_V3_BTN_LOCK 0x01U
|
||||
#define FORD_V3_BTN_UNLOCK 0x02U
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
|
||||
.te_short = FORD_V3_TE_SHORT,
|
||||
.te_long = FORD_V3_TE_LONG,
|
||||
.te_delta = FORD_V3_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V3_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV3DecoderStepReset = 0,
|
||||
FordV3DecoderStepPreamble = 1,
|
||||
FordV3DecoderStepData = 2,
|
||||
} FordV3DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV3 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
uint8_t bit_count;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint32_t serial;
|
||||
uint16_t counter;
|
||||
} SubGhzProtocolDecoderFordV3;
|
||||
|
||||
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
|
||||
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
|
||||
static const char* ford_v3_button_name(uint8_t btn);
|
||||
|
||||
static const char* ford_v3_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case FORD_V3_BTN_LOCK:
|
||||
return "Lock";
|
||||
case FORD_V3_BTN_UNLOCK:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static void ford_v3_add_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
|
||||
if(instance->bit_count >= FORD_V3_DATA_BITS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t byte_index = instance->bit_count / 8U;
|
||||
const uint8_t bit_in_byte = 7U - (instance->bit_count % 8U);
|
||||
if(bit) {
|
||||
instance->raw_bytes[byte_index] |= (uint8_t)(1U << bit_in_byte);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
|
||||
const uint8_t* b = instance->raw_bytes;
|
||||
|
||||
instance->serial = ((uint32_t)b[1] << 24) | ((uint32_t)b[2] << 16) | ((uint32_t)b[3] << 8) |
|
||||
(uint32_t)b[4];
|
||||
instance->counter =
|
||||
(uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
|
||||
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = (b[6] & 0x01U) ? FORD_V3_BTN_UNLOCK : FORD_V3_BTN_LOCK;
|
||||
instance->generic.cnt = instance->counter;
|
||||
}
|
||||
|
||||
static void ford_v3_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
|
||||
if(instance->bit_count < FORD_V3_DATA_BITS) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
ford_v3_parse_fields(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV3));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v3;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
ford_v3_reset_data(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV3DecoderStepReset:
|
||||
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
|
||||
ford_v3_reset_data(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepPreamble:
|
||||
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
|
||||
instance->preamble_count++;
|
||||
} else if(
|
||||
instance->preamble_count >= FORD_V3_PREAMBLE_MIN &&
|
||||
pp_is_long(duration, &subghz_protocol_ford_v3_const)) {
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
|
||||
const ManchesterEvent event =
|
||||
level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
|
||||
bool data_bit = false;
|
||||
const bool valid = manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit);
|
||||
if(valid) {
|
||||
ford_v3_add_bit(instance, data_bit);
|
||||
}
|
||||
instance->decoder.parser_step = FordV3DecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepData: {
|
||||
if(!pp_is_short(duration, &subghz_protocol_ford_v3_const) &&
|
||||
!pp_is_long(duration, &subghz_protocol_ford_v3_const)) {
|
||||
ford_v3_emit_if_ready(instance);
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
|
||||
if(pp_is_short(duration, &subghz_protocol_ford_v3_const)) {
|
||||
ford_v3_reset_data(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ManchesterEvent event;
|
||||
if(level) {
|
||||
event = pp_is_short(duration, &subghz_protocol_ford_v3_const) ? ManchesterEventShortHigh :
|
||||
ManchesterEventLongHigh;
|
||||
} else {
|
||||
event = pp_is_short(duration, &subghz_protocol_ford_v3_const) ? ManchesterEventShortLow :
|
||||
ManchesterEventLongLow;
|
||||
}
|
||||
|
||||
bool data_bit = false;
|
||||
const bool valid = manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit);
|
||||
|
||||
if(valid) {
|
||||
ford_v3_add_bit(instance, data_bit);
|
||||
if(instance->bit_count >= FORD_V3_DATA_BITS) {
|
||||
ford_v3_emit_if_ready(instance);
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
uint8_t hash = 0;
|
||||
|
||||
for(size_t i = 0; i < FORD_V3_DATA_BYTES; i++) {
|
||||
hash ^= instance->raw_bytes[i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
instance->generic.data = ((uint64_t)instance->raw_bytes[0] << 56) |
|
||||
((uint64_t)instance->raw_bytes[1] << 48) |
|
||||
((uint64_t)instance->raw_bytes[2] << 40) |
|
||||
((uint64_t)instance->raw_bytes[3] << 32) |
|
||||
((uint64_t)instance->raw_bytes[4] << 24) |
|
||||
((uint64_t)instance->raw_bytes[5] << 16) |
|
||||
((uint64_t)instance->raw_bytes[6] << 8) |
|
||||
(uint64_t)instance->raw_bytes[7];
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, "Raw", instance->raw_bytes, FORD_V3_DATA_BYTES);
|
||||
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->counter);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v3_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ford_v3_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
const uint64_t d = instance->generic.data;
|
||||
for(uint8_t i = 0; i < 8U; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(d >> (56 - i * 8));
|
||||
}
|
||||
memset(&instance->raw_bytes[8], 0, FORD_V3_DATA_BYTES - 8U);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_hex(flipper_format, "Raw", instance->raw_bytes, FORD_V3_DATA_BYTES);
|
||||
|
||||
instance->bit_count = FORD_V3_DATA_BITS;
|
||||
ford_v3_parse_fields(instance);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%08lX Btn:%02X %s\r\n"
|
||||
"Cnt:%04X Hop:%02X%02X%02X%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[0],
|
||||
k[1],
|
||||
k[2],
|
||||
k[3],
|
||||
k[4],
|
||||
k[5],
|
||||
k[6],
|
||||
k[7],
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v3_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter,
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12]);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v3_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_ford_v3_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v3_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v3_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v3_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v3_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v3_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ford_protocol_v3 = {
|
||||
.name = FORD_PROTOCOL_V3_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_ford_v3_decoder,
|
||||
.encoder = &subghz_protocol_ford_v3_encoder,
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define FORD_PROTOCOL_V3_NAME "Ford V3"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v3;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v3_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v3_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,701 @@
|
||||
#include "honda_static.h"
|
||||
#include "protocols_common.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
|
||||
#define HONDA_STATIC_BIT_COUNT 64
|
||||
#define HONDA_STATIC_MIN_SYMBOLS 36
|
||||
#define HONDA_STATIC_SHORT_BASE_US 28
|
||||
#define HONDA_STATIC_SHORT_SPAN_US 70
|
||||
#define HONDA_STATIC_LONG_BASE_US 61
|
||||
#define HONDA_STATIC_LONG_SPAN_US 130
|
||||
#define HONDA_STATIC_SYNC_TIME_US 700
|
||||
#define HONDA_STATIC_ELEMENT_TIME_US 63
|
||||
#define HONDA_STATIC_UPLOAD_CAPACITY \
|
||||
(1U + HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT + (2U * HONDA_STATIC_BIT_COUNT) + 1U)
|
||||
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||
#define HONDA_STATIC_SYMBOL_BYTE_COUNT ((HONDA_STATIC_SYMBOL_CAPACITY + 7U) / 8U)
|
||||
_Static_assert(
|
||||
HONDA_STATIC_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"HONDA_STATIC_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
|
||||
#endif
|
||||
static const char* const honda_static_button_names[9] = {
|
||||
"Lock",
|
||||
"Unlock",
|
||||
"Unknown",
|
||||
"Trunk",
|
||||
"Remote Start",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Panic",
|
||||
"Lock x2",
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t button;
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint8_t checksum;
|
||||
} HondaStaticFields;
|
||||
|
||||
struct SubGhzProtocolDecoderHondaStatic {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t symbols[HONDA_STATIC_SYMBOL_BYTE_COUNT];
|
||||
uint16_t symbols_count;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
struct SubGhzProtocolEncoderHondaStatic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
HondaStaticFields decoded;
|
||||
uint8_t tx_button;
|
||||
};
|
||||
#endif
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded);
|
||||
|
||||
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte = data[bit_index >> 3U];
|
||||
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||
}
|
||||
|
||||
return (uint8_t)value;
|
||||
}
|
||||
|
||||
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte = data[bit_index >> 3U];
|
||||
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte_index = bit_index >> 3U;
|
||||
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
|
||||
const uint8_t mask = (uint8_t)(1U << shift);
|
||||
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
|
||||
|
||||
if(bit) {
|
||||
data[byte_index] |= mask;
|
||||
} else {
|
||||
data[byte_index] &= (uint8_t)~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint8_t honda_static_level_u8(bool level) {
|
||||
return level ? 1U : 0U;
|
||||
}
|
||||
|
||||
static void honda_static_symbol_set(uint8_t* buf, uint16_t index, uint8_t v) {
|
||||
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||
const uint8_t mask = (uint8_t)(1U << shift);
|
||||
if(v) {
|
||||
buf[byte_index] |= mask;
|
||||
} else {
|
||||
buf[byte_index] &= (uint8_t)~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t honda_static_symbol_get(const uint8_t* buf, uint16_t index) {
|
||||
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||
return (uint8_t)((buf[byte_index] >> shift) & 1U);
|
||||
}
|
||||
|
||||
static bool honda_static_is_valid_button(uint8_t button) {
|
||||
if(button > 9U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((0x336U >> button) & 1U) != 0U;
|
||||
}
|
||||
|
||||
static bool honda_static_is_valid_serial(uint32_t serial) {
|
||||
return (serial != 0U) && (serial != 0x0FFFFFFFU);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
|
||||
if(button < 2U) {
|
||||
return 1U;
|
||||
}
|
||||
button -= 2U;
|
||||
if(button <= 3U) {
|
||||
return honda_static_encoder_button_map[button];
|
||||
}
|
||||
|
||||
return 1U;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char* honda_static_button_name(uint8_t button) {
|
||||
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
|
||||
return honda_static_button_names[button - 1U];
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
|
||||
const uint8_t canonical[7] = {
|
||||
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
|
||||
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
|
||||
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
|
||||
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
|
||||
compact[5],
|
||||
compact[6],
|
||||
compact[7],
|
||||
};
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
|
||||
checksum ^= canonical[i];
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
|
||||
uint8_t compact[8];
|
||||
pp_u64_to_bytes_be(key, compact);
|
||||
|
||||
memset(fields, 0, sizeof(*fields));
|
||||
fields->button = compact[0] & 0x0FU;
|
||||
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
|
||||
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
|
||||
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
|
||||
(uint32_t)compact[7];
|
||||
fields->checksum = honda_static_compact_bytes_checksum(compact);
|
||||
}
|
||||
|
||||
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
|
||||
uint8_t compact[8];
|
||||
|
||||
compact[0] = fields->button & 0x0FU;
|
||||
compact[1] = (uint8_t)(fields->serial >> 20U);
|
||||
compact[2] = (uint8_t)(fields->serial >> 12U);
|
||||
compact[3] = (uint8_t)(fields->serial >> 4U);
|
||||
compact[4] = (uint8_t)(fields->serial << 4U);
|
||||
compact[5] = (uint8_t)(fields->counter >> 16U);
|
||||
compact[6] = (uint8_t)(fields->counter >> 8U);
|
||||
compact[7] = (uint8_t)fields->counter;
|
||||
|
||||
return pp_bytes_to_u64_be(compact);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
|
||||
memset(packet, 0, 8);
|
||||
|
||||
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
|
||||
honda_static_set_bits(packet, 4, 28, fields->serial);
|
||||
honda_static_set_bits(packet, 32, 24, fields->counter);
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum ^= packet[i];
|
||||
}
|
||||
|
||||
honda_static_set_bits(packet, 56, 8, checksum);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool
|
||||
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||
const uint8_t button = honda_static_get_bits(packet, 0, 4);
|
||||
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
|
||||
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
|
||||
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
|
||||
|
||||
uint8_t checksum_calc = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum_calc ^= packet[i];
|
||||
}
|
||||
|
||||
if(checksum != checksum_calc) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_button(button)) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_serial(serial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fields->button = button;
|
||||
fields->serial = serial;
|
||||
fields->counter = counter;
|
||||
fields->checksum = checksum;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||
uint8_t reversed[9];
|
||||
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
|
||||
reversed[i] = pp_reverse_bits8(packet[i]);
|
||||
}
|
||||
|
||||
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
|
||||
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
|
||||
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum ^= reversed[i];
|
||||
}
|
||||
|
||||
if(!honda_static_is_valid_button(button)) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_serial(serial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fields->button = button;
|
||||
fields->serial = serial;
|
||||
fields->counter = counter;
|
||||
fields->checksum = checksum;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool honda_static_manchester_pack_64(
|
||||
const uint8_t* symbol_bits,
|
||||
uint16_t count,
|
||||
uint16_t start_pos,
|
||||
bool inverted,
|
||||
uint8_t packet[9],
|
||||
uint16_t* out_bit_count) {
|
||||
memset(packet, 0, 9);
|
||||
|
||||
uint16_t pos = start_pos;
|
||||
uint16_t bit_count = 0U;
|
||||
|
||||
while((uint16_t)(pos + 1U) < count) {
|
||||
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t a = honda_static_symbol_get(symbol_bits, pos);
|
||||
const uint8_t b = honda_static_symbol_get(symbol_bits, pos + 1U);
|
||||
|
||||
if(a == b) {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bit = false;
|
||||
if(inverted) {
|
||||
bit = (a == 0U) && (b == 1U);
|
||||
} else {
|
||||
bit = (a == 1U) && (b == 0U);
|
||||
}
|
||||
|
||||
if(bit) {
|
||||
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
|
||||
}
|
||||
|
||||
bit_count++;
|
||||
pos += 2U;
|
||||
}
|
||||
|
||||
if(out_bit_count) {
|
||||
*out_bit_count = bit_count;
|
||||
}
|
||||
|
||||
return bit_count >= HONDA_STATIC_BIT_COUNT;
|
||||
}
|
||||
|
||||
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
|
||||
const uint16_t count = instance->symbols_count;
|
||||
const uint8_t* symbol_bits = instance->symbols;
|
||||
HondaStaticFields decoded;
|
||||
|
||||
uint16_t index = 1U;
|
||||
uint16_t transitions = 0U;
|
||||
|
||||
while(index < count) {
|
||||
if(honda_static_symbol_get(symbol_bits, index) !=
|
||||
honda_static_symbol_get(symbol_bits, index - 1U)) {
|
||||
transitions++;
|
||||
} else {
|
||||
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
|
||||
break;
|
||||
}
|
||||
transitions = 0U;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index >= count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while(((uint16_t)(index + 1U) < count) && (honda_static_symbol_get(symbol_bits, index) ==
|
||||
honda_static_symbol_get(symbol_bits, index + 1U))) {
|
||||
index++;
|
||||
}
|
||||
|
||||
const uint16_t data_start = index;
|
||||
|
||||
uint8_t packet[9] = {0};
|
||||
uint16_t bit_count = 0U;
|
||||
|
||||
if(!honda_static_manchester_pack_64(
|
||||
symbol_bits, count, data_start, inverted, packet, &bit_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_forward_packet(packet, &decoded)) {
|
||||
honda_static_decoder_commit(instance, &decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(inverted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_reverse_packet(packet, &decoded)) {
|
||||
honda_static_decoder_commit(instance, &decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded) {
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(decoded);
|
||||
instance->generic.serial = decoded->serial;
|
||||
instance->generic.cnt = decoded->counter;
|
||||
instance->generic.btn = decoded->button;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
|
||||
uint8_t packet[8];
|
||||
honda_static_build_packet_bytes(&instance->decoded, packet);
|
||||
|
||||
size_t index = 0U;
|
||||
LevelDuration* up = instance->encoder.upload;
|
||||
const size_t cap = HONDA_STATIC_UPLOAD_CAPACITY;
|
||||
|
||||
index = pp_emit(up, index, cap, true, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||
index = pp_emit(up, index, cap, (i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
|
||||
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
|
||||
index = pp_emit(up, index, cap, !value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
index = pp_emit(up, index, cap, value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||
index = pp_emit(up, index, cap, !last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_static_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_honda_static_feed,
|
||||
.reset = subghz_protocol_decoder_honda_static_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_honda_static_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
|
||||
.get_string = subghz_protocol_decoder_honda_static_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_static_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol honda_static_protocol = {
|
||||
.name = HONDA_STATIC_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_honda_static_decoder,
|
||||
.encoder = &subghz_protocol_honda_static_encoder,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_static_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 3U;
|
||||
pp_encoder_buffer_ensure(instance, HONDA_STATIC_UPLOAD_CAPACITY);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
honda_static_unpack_compact(key, &instance->decoded);
|
||||
|
||||
uint32_t serial = instance->decoded.serial;
|
||||
uint32_t btn_u32 = instance->decoded.button;
|
||||
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn_u32, &cnt, NULL);
|
||||
|
||||
instance->decoded.serial = serial;
|
||||
uint8_t b = (uint8_t)btn_u32;
|
||||
if(honda_static_is_valid_button(b)) {
|
||||
instance->decoded.button = b;
|
||||
} else if(b >= 2U && b <= 5U) {
|
||||
instance->decoded.button = honda_static_encoder_remap_button(b);
|
||||
}
|
||||
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
|
||||
uint8_t key_data[8];
|
||||
pp_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) {
|
||||
return SubGhzProtocolStatusErrorParserKey;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 3U);
|
||||
|
||||
honda_static_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_static_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
instance->symbols_count = 0U;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
|
||||
const uint8_t sym = honda_static_level_u8(level);
|
||||
|
||||
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
|
||||
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
|
||||
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
|
||||
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
|
||||
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t sc = instance->symbols_count;
|
||||
|
||||
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
|
||||
if(!honda_static_parse_symbols(instance, true)) {
|
||||
honda_static_parse_symbols(instance, false);
|
||||
}
|
||||
}
|
||||
|
||||
instance->symbols_count = 0U;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
const uint64_t data = instance->generic.data;
|
||||
|
||||
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
|
||||
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Btn:%s\r\n"
|
||||
"Ser:%07lX Cnt:%06lX",
|
||||
instance->generic.protocol_name,
|
||||
(unsigned long long)instance->generic.data,
|
||||
honda_static_button_name(decoded.button),
|
||||
(unsigned long)decoded.serial,
|
||||
(unsigned long)decoded.counter);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
decoded.serial,
|
||||
decoded.button,
|
||||
decoded.counter,
|
||||
0);
|
||||
if(status != SubGhzProtocolStatusOk) return status;
|
||||
|
||||
uint32_t temp = decoded.checksum;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
uint32_t s = decoded.serial;
|
||||
uint32_t b = decoded.button;
|
||||
uint32_t c = decoded.counter;
|
||||
pp_encoder_read_fields(flipper_format, &s, &b, &c, NULL);
|
||||
decoded.serial = s;
|
||||
decoded.button = (uint8_t)b;
|
||||
decoded.counter = c & 0x00FFFFFFU;
|
||||
|
||||
instance->generic.data = honda_static_pack_compact(&decoded);
|
||||
instance->generic.serial = decoded.serial;
|
||||
instance->generic.cnt = decoded.counter;
|
||||
instance->generic.btn = decoded.button;
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
|
||||
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
|
||||
|
||||
extern const SubGhzProtocol honda_static_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_honda_static_free(void* context);
|
||||
void subghz_protocol_decoder_honda_static_reset(void* context);
|
||||
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
#endif
|
||||
@@ -0,0 +1,150 @@
|
||||
#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>
|
||||
|
||||
#include <m-array.h>
|
||||
|
||||
// Extracts bit number n from integer x
|
||||
#define bit(x, n) (((x) >> (n)) & 1)
|
||||
|
||||
// Builds a 5-bit number from five selected bit positions in x
|
||||
#define g5(x, a, b, c, d, e) \
|
||||
(bit(x, a) + bit(x, b) * 2 + bit(x, c) * 4 + bit(x, d) * 8 + bit(x, e) * 16)
|
||||
|
||||
struct SubGhzKeystore {
|
||||
SubGhzKeyArray_t data;
|
||||
const char* mfname;
|
||||
uint8_t kl_type;
|
||||
};
|
||||
|
||||
/*
|
||||
* Keeloq
|
||||
* https://ru.wikipedia.org/wiki/KeeLoq
|
||||
* https://phreakerclub.com/forum/showthread.php?t=1094
|
||||
*
|
||||
*/
|
||||
#define KEELOQ_NLF 0x3A5C742E
|
||||
|
||||
/*
|
||||
* KeeLoq learning types
|
||||
* https://phreakerclub.com/forum/showthread.php?t=67
|
||||
*/
|
||||
#define KEELOQ_LEARNING_UNKNOWN 0u
|
||||
#define KEELOQ_LEARNING_SIMPLE 1u
|
||||
#define KEELOQ_LEARNING_NORMAL 2u
|
||||
// #define KEELOQ_LEARNING_SECURE 3u
|
||||
#define KEELOQ_LEARNING_MAGIC_XOR_TYPE_1 4u
|
||||
// #define KEELOQ_LEARNING_FAAC 5u
|
||||
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_1 6u
|
||||
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_2 7u
|
||||
#define KEELOQ_LEARNING_MAGIC_SERIAL_TYPE_3 8u
|
||||
|
||||
/**
|
||||
* Simple Learning Encrypt
|
||||
* @param data - 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
|
||||
* @param key - manufacture (64bit)
|
||||
* @return keeloq encrypt data
|
||||
*/
|
||||
static inline uint32_t
|
||||
subghz_protocol_keeloq_common_encrypt(const uint32_t data, const uint64_t key) {
|
||||
uint32_t x = data, r;
|
||||
for(r = 0; r < 528; r++)
|
||||
x = (x >> 1) ^ ((bit(x, 0) ^ bit(x, 16) ^ (uint32_t)bit(key, r & 63) ^
|
||||
bit(KEELOQ_NLF, g5(x, 1, 9, 20, 26, 31)))
|
||||
<< 31);
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple Learning Decrypt
|
||||
* @param data - keeloq encrypt data
|
||||
* @param key - manufacture (64bit)
|
||||
* @return 0xBSSSCCCC, B(4bit) key, S(10bit) serial&0x3FF, C(16bit) counter
|
||||
*/
|
||||
static inline uint32_t
|
||||
subghz_protocol_keeloq_common_decrypt(const uint32_t data, const uint64_t key) {
|
||||
uint32_t x = data, r;
|
||||
for(r = 0; r < 528; r++)
|
||||
x = (x << 1) ^ bit(x, 31) ^ bit(x, 15) ^ (uint32_t)bit(key, (15 - r) & 63) ^
|
||||
bit(KEELOQ_NLF, g5(x, 0, 8, 19, 25, 30));
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normal Learning
|
||||
* @param data - serial number (28bit)
|
||||
* @param key - manufacture (64bit)
|
||||
* @return manufacture for this serial number (64bit)
|
||||
*/
|
||||
static inline uint64_t
|
||||
subghz_protocol_keeloq_common_normal_learning(uint32_t data, const uint64_t key) {
|
||||
uint32_t k1, k2;
|
||||
|
||||
data &= 0x0FFFFFFF;
|
||||
data |= 0x20000000;
|
||||
k1 = subghz_protocol_keeloq_common_decrypt(data, key);
|
||||
|
||||
data &= 0x0FFFFFFF;
|
||||
data |= 0x60000000;
|
||||
k2 = subghz_protocol_keeloq_common_decrypt(data, key);
|
||||
|
||||
return ((uint64_t)k2 << 32) | k1; // key - shifrovanoya
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic_xor_type1 Learning
|
||||
* @param data - serial number (28bit)
|
||||
* @param xor - magic xor (64bit)
|
||||
* @return manufacture for this serial number (64bit)
|
||||
*/
|
||||
static inline uint64_t
|
||||
subghz_protocol_keeloq_common_magic_xor_type1_learning(uint32_t data, uint64_t xor) {
|
||||
data &= 0x0FFFFFFF;
|
||||
return (((uint64_t)data << 32) | data) ^ xor;
|
||||
}
|
||||
|
||||
/** Magic_serial_type1 Learning
|
||||
* @param data - serial number (28bit)
|
||||
* @param man - magic man (64bit)
|
||||
* @return manufacture for this serial number (64bit)
|
||||
*/
|
||||
static inline uint64_t
|
||||
subghz_protocol_keeloq_common_magic_serial_type1_learning(uint32_t data, uint64_t man) {
|
||||
return (man & 0xFFFFFFFF) | ((uint64_t)data << 40) |
|
||||
((uint64_t)(((data & 0xff) + ((data >> 8) & 0xFF)) & 0xFF) << 32);
|
||||
}
|
||||
|
||||
/** Magic_serial_type2 Learning
|
||||
* @param data - btn+serial number (32bit)
|
||||
* @param man - magic man (64bit)
|
||||
* @return manufacture for this serial number (64bit)
|
||||
*/
|
||||
static inline uint64_t
|
||||
subghz_protocol_keeloq_common_magic_serial_type2_learning(uint32_t data, uint64_t man) {
|
||||
uint8_t* p = (uint8_t*)&data;
|
||||
uint8_t* m = (uint8_t*)&man;
|
||||
m[7] = p[0];
|
||||
m[6] = p[1];
|
||||
m[5] = p[2];
|
||||
m[4] = p[3];
|
||||
return man;
|
||||
}
|
||||
|
||||
/** Magic_serial_type3 Learning
|
||||
* @param data - btn+serial number (32bit)
|
||||
* @param man - magic man (64bit)
|
||||
* @return manufacture for this serial number (64bit)
|
||||
*/
|
||||
static inline uint64_t
|
||||
subghz_protocol_keeloq_common_magic_serial_type3_learning(uint32_t data, uint64_t man) {
|
||||
return (man & 0xFFFFFFFFFF000000) | (data & 0xFFFFFF);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#include "keys.h"
|
||||
|
||||
#define KIA_KEY1 10u
|
||||
#define KIA_KEY2 11u
|
||||
#define KIA_KEY3 12u
|
||||
#define KIA_KEY4 13u
|
||||
|
||||
uint64_t kia_mf_key = 0;
|
||||
uint64_t kia_v6_a_key = 0;
|
||||
uint64_t kia_v6_b_key = 0;
|
||||
uint64_t kia_v5_key = 0;
|
||||
|
||||
void protopirate_keys_load(SubGhzEnvironment* environment) {
|
||||
SubGhzKeystore* keystore = subghz_environment_get_keystore(environment);
|
||||
// Load keys from secure keystore
|
||||
for
|
||||
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
|
||||
switch(manufacture_code->type) {
|
||||
case KIA_KEY1:
|
||||
kia_mf_key = manufacture_code->key;
|
||||
break;
|
||||
case KIA_KEY2:
|
||||
kia_v6_a_key = manufacture_code->key;
|
||||
break;
|
||||
case KIA_KEY3:
|
||||
kia_v6_b_key = manufacture_code->key;
|
||||
break;
|
||||
case KIA_KEY4:
|
||||
kia_v5_key = manufacture_code->key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t get_kia_mf_key() {
|
||||
return kia_mf_key;
|
||||
}
|
||||
|
||||
uint64_t get_kia_v6_keystore_a() {
|
||||
return kia_v6_a_key;
|
||||
}
|
||||
|
||||
uint64_t get_kia_v6_keystore_b() {
|
||||
return kia_v6_b_key;
|
||||
}
|
||||
|
||||
uint64_t get_kia_v5_key() {
|
||||
return kia_v5_key;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lib/subghz/environment.h>
|
||||
#include <lib/subghz/subghz_keystore.h>
|
||||
|
||||
extern uint64_t kia_mf_key;
|
||||
extern uint64_t kia_v6_a_key;
|
||||
extern uint64_t kia_v6_b_key;
|
||||
extern uint64_t kia_v5_key;
|
||||
|
||||
uint64_t get_kia_mf_key();
|
||||
|
||||
uint64_t get_kia_v6_keystore_a();
|
||||
|
||||
uint64_t get_kia_v6_keystore_b();
|
||||
|
||||
uint64_t get_kia_v5_key();
|
||||
|
||||
void protopirate_keys_load(SubGhzEnvironment* environment);
|
||||
@@ -0,0 +1,11 @@
|
||||
#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>
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "kia_generic.h"
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V0_NAME "Kia V0"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKIA SubGhzProtocolDecoderKIA;
|
||||
typedef struct SubGhzProtocolEncoderKIA SubGhzProtocolEncoderKIA;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_kia_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_kia_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v0;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_kia_free(void* context);
|
||||
void subghz_protocol_decoder_kia_reset(void* context);
|
||||
void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder helper functions
|
||||
void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button);
|
||||
void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter);
|
||||
void subghz_protocol_encoder_kia_increment_counter(void* context);
|
||||
uint16_t subghz_protocol_encoder_kia_get_counter(void* context);
|
||||
uint8_t subghz_protocol_encoder_kia_get_button(void* context);
|
||||
@@ -0,0 +1,494 @@
|
||||
#include "kia_v1.h"
|
||||
#include "protocols_common.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "KiaV1"
|
||||
|
||||
#define KIA_V1_TOTAL_BURSTS 3
|
||||
#define KIA_V1_INTER_BURST_GAP_US 25000
|
||||
#define KIA_V1_HEADER_PULSES 90
|
||||
#define KIA_V1_UPLOAD_CAPACITY \
|
||||
((KIA_V1_TOTAL_BURSTS * ((KIA_V1_HEADER_PULSES * 2) + 1 + ((57U - 1U) * 2))) + \
|
||||
(KIA_V1_TOTAL_BURSTS - 1))
|
||||
_Static_assert(
|
||||
KIA_V1_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V1_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v1_const = {
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 57,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
ManchesterState manchester_saved_state;
|
||||
uint8_t crc;
|
||||
bool crc_check;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV1 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV1DecoderStepReset = 0,
|
||||
KiaV1DecoderStepCheckPreamble,
|
||||
KiaV1DecoderStepDecodeData,
|
||||
} KiaV1DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v1_decoder = {
|
||||
.alloc = kia_protocol_decoder_v1_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
|
||||
.feed = kia_protocol_decoder_v1_feed,
|
||||
.reset = kia_protocol_decoder_v1_reset,
|
||||
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = kia_protocol_decoder_v1_serialize,
|
||||
.deserialize = kia_protocol_decoder_v1_deserialize,
|
||||
.get_string = kia_protocol_decoder_v1_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v1_encoder = {
|
||||
.alloc = kia_protocol_encoder_v1_alloc,
|
||||
.free = pp_encoder_free,
|
||||
|
||||
.deserialize = kia_protocol_encoder_v1_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v1_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v1 = {
|
||||
.name = KIA_PROTOCOL_V1_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &kia_protocol_v1_decoder,
|
||||
.encoder = &kia_protocol_v1_encoder,
|
||||
};
|
||||
|
||||
static void kia_v1_check_remote_controller(SubGhzProtocolDecoderKiaV1* instance);
|
||||
|
||||
static uint8_t kia_v1_crc4(const uint8_t* bytes, int count, uint8_t offset) {
|
||||
uint8_t crc = 0;
|
||||
|
||||
for(int i = 0; i < count; i++) {
|
||||
uint8_t b = bytes[i];
|
||||
crc ^= ((b & 0x0F) ^ (b >> 4));
|
||||
}
|
||||
|
||||
crc = (crc + offset) & 0x0F;
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void kia_v1_check_remote_controller(SubGhzProtocolDecoderKiaV1* instance) {
|
||||
instance->generic.serial = instance->generic.data >> 24;
|
||||
instance->generic.btn = (instance->generic.data >> 16) & 0xFF;
|
||||
instance->generic.cnt = ((instance->generic.data >> 4) & 0xF) << 8 |
|
||||
((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
|
||||
uint8_t char_data[7];
|
||||
char_data[0] = (instance->generic.serial >> 24) & 0xFF;
|
||||
char_data[1] = (instance->generic.serial >> 16) & 0xFF;
|
||||
char_data[2] = (instance->generic.serial >> 8) & 0xFF;
|
||||
char_data[3] = instance->generic.serial & 0xFF;
|
||||
char_data[4] = instance->generic.btn;
|
||||
char_data[5] = instance->generic.cnt & 0xFF;
|
||||
|
||||
char_data[6] = cnt_high;
|
||||
uint8_t crc = kia_v1_crc4(char_data, 7, 1);
|
||||
|
||||
instance->crc = cnt_high << 4 | crc;
|
||||
instance->crc_check = (crc == (instance->generic.data & 0xF));
|
||||
}
|
||||
|
||||
static const char* kia_v1_get_button_name(uint8_t btn) {
|
||||
const char* name;
|
||||
switch(btn) {
|
||||
case 0x1:
|
||||
name = "Close";
|
||||
break;
|
||||
case 0x2:
|
||||
name = "Open";
|
||||
break;
|
||||
case 0x3:
|
||||
name = "Boot";
|
||||
break;
|
||||
default:
|
||||
name = "??";
|
||||
break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV1* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV1));
|
||||
|
||||
instance->base.protocol = &kia_protocol_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
return instance;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static void kia_protocol_encoder_v1_get_upload(SubGhzProtocolEncoderKiaV1* instance) {
|
||||
furi_check(instance);
|
||||
if(instance->encoder.upload == NULL) return; // lazy buffer not yet allocated
|
||||
size_t index = 0;
|
||||
|
||||
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
|
||||
uint8_t char_data[7];
|
||||
char_data[0] = (instance->generic.serial >> 24) & 0xFF;
|
||||
char_data[1] = (instance->generic.serial >> 16) & 0xFF;
|
||||
char_data[2] = (instance->generic.serial >> 8) & 0xFF;
|
||||
char_data[3] = instance->generic.serial & 0xFF;
|
||||
char_data[4] = instance->generic.btn;
|
||||
char_data[5] = instance->generic.cnt & 0xFF;
|
||||
|
||||
char_data[6] = cnt_high;
|
||||
uint8_t crc = kia_v1_crc4(char_data, 7, 1);
|
||||
|
||||
instance->generic.data = (uint64_t)instance->generic.serial << 24 |
|
||||
instance->generic.btn << 16 | (instance->generic.cnt & 0xFF) << 8 |
|
||||
((instance->generic.cnt >> 8) & 0xF) << 4 | crc;
|
||||
|
||||
for(uint8_t burst = 0; burst < KIA_V1_TOTAL_BURSTS; burst++) {
|
||||
if(burst > 0) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, KIA_V1_INTER_BURST_GAP_US);
|
||||
}
|
||||
|
||||
for(int i = 0; i < KIA_V1_HEADER_PULSES; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_long);
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
|
||||
if(bit_read(instance->generic.data, i - 2)) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v1_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v1_const.te_short);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Upload built: %d bursts, size_upload=%zu, data_count_bit=%u, data=0x%016llX",
|
||||
KIA_V1_TOTAL_BURSTS,
|
||||
instance->encoder.size_upload,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.data);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
do {
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "Missing or wrong Protocol");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t bits = 0;
|
||||
if(pp_encoder_read_bit(flipper_format, NULL, 0, &bits) != SubGhzProtocolStatusOk) break;
|
||||
|
||||
instance->generic.data_count_bit = kia_protocol_v1_const.min_count_bit_for_found;
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) break;
|
||||
|
||||
instance->generic.data = key;
|
||||
if(instance->generic.data == 0) break;
|
||||
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) break;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
instance->generic.cnt = (uint16_t)cnt;
|
||||
|
||||
instance->encoder.repeat = (int32_t)pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
pp_encoder_buffer_ensure(instance, KIA_V1_UPLOAD_CAPACITY);
|
||||
kia_protocol_encoder_v1_get_upload(instance);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder deserialized: repeat=%u, size_upload=%zu, is_running=%d, front=%zu",
|
||||
instance->encoder.repeat,
|
||||
instance->encoder.size_upload,
|
||||
instance->encoder.is_running,
|
||||
instance->encoder.front);
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v1_set_button(void* context, uint8_t button) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.btn = button & 0xFF;
|
||||
kia_protocol_encoder_v1_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Button set to 0x%02X, upload rebuilt with new CRC", instance->generic.btn);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.cnt = counter & 0xFFF;
|
||||
kia_protocol_encoder_v1_get_upload(instance);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Counter set to 0x%03X, upload rebuilt with new CRC",
|
||||
(uint16_t)instance->generic.cnt);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v1_increment_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.cnt = (instance->generic.cnt + 1) & 0xFFF;
|
||||
kia_protocol_encoder_v1_get_upload(instance);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Counter incremented to 0x%03X, upload rebuilt with new CRC",
|
||||
(uint16_t)instance->generic.cnt);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
uint16_t kia_protocol_encoder_v1_get_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
return instance->generic.cnt;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
uint8_t kia_protocol_encoder_v1_get_button(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
return instance->generic.btn;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void* kia_protocol_decoder_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV1* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV1));
|
||||
instance->base.protocol = &kia_protocol_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
instance->decoder.parser_step = KiaV1DecoderStepReset;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV1DecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, kia_protocol_v1_const.te_long) <
|
||||
kia_protocol_v1_const.te_delta)) {
|
||||
instance->decoder.parser_step = KiaV1DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV1DecoderStepCheckPreamble:
|
||||
if(!level) {
|
||||
if((DURATION_DIFF(duration, kia_protocol_v1_const.te_long) <
|
||||
kia_protocol_v1_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v1_const.te_long) <
|
||||
kia_protocol_v1_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV1DecoderStepReset;
|
||||
}
|
||||
}
|
||||
if(instance->header_count > 70) {
|
||||
if((!level) &&
|
||||
(DURATION_DIFF(duration, kia_protocol_v1_const.te_short) <
|
||||
kia_protocol_v1_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v1_const.te_long) <
|
||||
kia_protocol_v1_const.te_delta)) {
|
||||
instance->decoder.decode_count_bit = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->header_count = 0;
|
||||
instance->decoder.parser_step = KiaV1DecoderStepDecodeData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV1DecoderStepDecodeData:
|
||||
event = pp_manchester_event(duration, level, &kia_protocol_v1_const);
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
|
||||
if(data_ok) {
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data;
|
||||
instance->decoder.decode_count_bit++;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit == kia_protocol_v1_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = KiaV1DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
|
||||
kia_v1_check_remote_controller(instance);
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
||||
|
||||
return pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
flipper_format_rewind(flipper_format);
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, kia_protocol_v1_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
|
||||
kia_v1_check_remote_controller(instance);
|
||||
uint32_t code_found_hi = instance->generic.data >> 32;
|
||||
uint32_t code_found_lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%06lX%08lX\r\n"
|
||||
"Serial:%08lX\r\n"
|
||||
"Cnt:%03lX CRC:%01X %s\r\n"
|
||||
"Btn:%02X:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
code_found_hi,
|
||||
code_found_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->crc,
|
||||
instance->crc_check ? "OK" : "WRONG",
|
||||
instance->generic.btn,
|
||||
kia_v1_get_button_name(instance->generic.btn));
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "kia_generic.h"
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V1_NAME "Kia V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV1 SubGhzProtocolDecoderKiaV1;
|
||||
typedef struct SubGhzProtocolEncoderKiaV1 SubGhzProtocolEncoderKiaV1;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v1_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v1_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v1;
|
||||
|
||||
// Decoder functions
|
||||
void* kia_protocol_decoder_v1_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v1_free(void* context);
|
||||
void kia_protocol_decoder_v1_reset(void* context);
|
||||
void kia_protocol_decoder_v1_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v1_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* kia_protocol_encoder_v1_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
// Encoder helper functions for UI
|
||||
void kia_protocol_encoder_v1_set_button(void* context, uint8_t button);
|
||||
void kia_protocol_encoder_v1_set_counter(void* context, uint16_t counter);
|
||||
void kia_protocol_encoder_v1_increment_counter(void* context);
|
||||
uint16_t kia_protocol_encoder_v1_get_counter(void* context);
|
||||
uint8_t kia_protocol_encoder_v1_get_button(void* context);
|
||||
@@ -0,0 +1,429 @@
|
||||
#include "kia_v2.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "KiaV2"
|
||||
|
||||
#define KIA_V2_HEADER_PAIRS 252
|
||||
#define KIA_V2_TOTAL_BURSTS 2
|
||||
#define KIA_V2_UPLOAD_CAPACITY \
|
||||
(KIA_V2_TOTAL_BURSTS * ((KIA_V2_HEADER_PAIRS * 2) + 1 + ((53U - 1U) * 2)))
|
||||
_Static_assert(
|
||||
KIA_V2_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V2_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v2_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 53,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV2DecoderStepReset = 0,
|
||||
KiaV2DecoderStepCheckPreamble,
|
||||
KiaV2DecoderStepCollectRawBits,
|
||||
} KiaV2DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v2_decoder = {
|
||||
.alloc = kia_protocol_decoder_v2_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = kia_protocol_decoder_v2_feed,
|
||||
.reset = kia_protocol_decoder_v2_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = kia_protocol_decoder_v2_serialize,
|
||||
.deserialize = kia_protocol_decoder_v2_deserialize,
|
||||
.get_string = kia_protocol_decoder_v2_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v2_encoder = {
|
||||
.alloc = kia_protocol_encoder_v2_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = kia_protocol_encoder_v2_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v2_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v2 = {
|
||||
.name = KIA_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &kia_protocol_v2_decoder,
|
||||
.encoder = &kia_protocol_v2_encoder,
|
||||
};
|
||||
|
||||
static uint8_t kia_v2_calculate_crc(uint64_t data) {
|
||||
// Remove the CRC nibble (last 4 bits) to get the actual data
|
||||
uint64_t data_without_crc = data >> 4;
|
||||
|
||||
// Extract 6 bytes from the data
|
||||
uint8_t bytes[6];
|
||||
bytes[0] = (uint8_t)(data_without_crc);
|
||||
bytes[1] = (uint8_t)(data_without_crc >> 8);
|
||||
bytes[2] = (uint8_t)(data_without_crc >> 16);
|
||||
bytes[3] = (uint8_t)(data_without_crc >> 24);
|
||||
bytes[4] = (uint8_t)(data_without_crc >> 32);
|
||||
bytes[5] = (uint8_t)(data_without_crc >> 40);
|
||||
|
||||
uint8_t crc = 0;
|
||||
for(int i = 0; i < 6; i++) {
|
||||
crc ^= (bytes[i] & 0x0F) ^ (bytes[i] >> 4);
|
||||
}
|
||||
|
||||
return (crc + 1) & 0x0F;
|
||||
}
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static void kia_protocol_encoder_v2_get_upload(SubGhzProtocolEncoderKiaV2* instance) {
|
||||
furi_check(instance);
|
||||
if(instance->encoder.upload == NULL) return;
|
||||
size_t index = 0;
|
||||
|
||||
uint8_t crc = kia_v2_calculate_crc(instance->generic.data);
|
||||
instance->generic.data = (instance->generic.data & ~0x0FULL) | crc;
|
||||
|
||||
for(uint8_t burst = 0; burst < KIA_V2_TOTAL_BURSTS; burst++) {
|
||||
for(int i = 0; i < KIA_V2_HEADER_PAIRS; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_long);
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
|
||||
bool bit = bit_read(instance->generic.data, i - 2);
|
||||
|
||||
if(bit) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)kia_protocol_v2_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)kia_protocol_v2_const.te_short);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Upload built: %d bursts, size_upload=%zu, data_count_bit=%u, data=0x%016llX",
|
||||
KIA_V2_TOTAL_BURSTS,
|
||||
instance->encoder.size_upload,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.data);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV2* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV2));
|
||||
|
||||
instance->base.protocol = &kia_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV2* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
do {
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "Missing or wrong Protocol");
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t bits = 0;
|
||||
if(pp_encoder_read_bit(flipper_format, NULL, 0, &bits) != SubGhzProtocolStatusOk) break;
|
||||
|
||||
instance->generic.data_count_bit = kia_protocol_v2_const.min_count_bit_for_found;
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &key)) break;
|
||||
|
||||
instance->generic.data = key;
|
||||
if(instance->generic.data == 0) break;
|
||||
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) break;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
instance->generic.cnt = (uint16_t)cnt;
|
||||
|
||||
uint64_t new_data = 0;
|
||||
|
||||
new_data |= 1ULL << 52;
|
||||
|
||||
new_data |= ((uint64_t)instance->generic.serial << 20) & 0xFFFFFFFFF00000ULL;
|
||||
|
||||
uint32_t uVar6 = ((uint32_t)(instance->generic.cnt & 0xFF) << 8) |
|
||||
((uint32_t)(instance->generic.btn & 0x0F) << 16) |
|
||||
((uint32_t)(instance->generic.cnt >> 4) & 0xF0);
|
||||
|
||||
new_data |= (uint64_t)uVar6;
|
||||
|
||||
instance->generic.data = new_data;
|
||||
instance->generic.data_count_bit = 53;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder reconstruct: serial=0x%08lX, btn=0x%X, cnt=0x%03lX, uVar6=0x%05lX, data=0x%016llX",
|
||||
(unsigned long)instance->generic.serial,
|
||||
(unsigned int)instance->generic.btn,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
(unsigned long)uVar6,
|
||||
(unsigned long long)instance->generic.data);
|
||||
|
||||
instance->encoder.repeat = (int32_t)pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
pp_encoder_buffer_ensure(instance, KIA_V2_UPLOAD_CAPACITY);
|
||||
kia_protocol_encoder_v2_get_upload(instance);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder deserialized: repeat=%u, size_upload=%zu, is_running=%d, front=%zu",
|
||||
instance->encoder.repeat,
|
||||
instance->encoder.size_upload,
|
||||
instance->encoder.is_running,
|
||||
instance->encoder.front);
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void* kia_protocol_decoder_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV2* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV2));
|
||||
instance->base.protocol = &kia_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV2DecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, kia_protocol_v2_const.te_long) <
|
||||
kia_protocol_v2_const.te_delta)) {
|
||||
instance->decoder.parser_step = KiaV2DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV2DecoderStepCheckPreamble:
|
||||
if(level) // HIGH pulse
|
||||
{
|
||||
if(DURATION_DIFF(duration, kia_protocol_v2_const.te_long) <
|
||||
kia_protocol_v2_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v2_const.te_short) <
|
||||
kia_protocol_v2_const.te_delta) {
|
||||
if(instance->header_count >= 100) {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 1;
|
||||
instance->decoder.parser_step = KiaV2DecoderStepCollectRawBits;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, kia_protocol_v2_const.te_long) <
|
||||
kia_protocol_v2_const.te_delta) {
|
||||
instance->header_count++;
|
||||
instance->decoder.te_last = duration;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v2_const.te_short) <
|
||||
kia_protocol_v2_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV2DecoderStepCollectRawBits: {
|
||||
ManchesterEvent event = pp_manchester_event(duration, level, &kia_protocol_v2_const);
|
||||
if(event == ManchesterEventReset) {
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
bool data_bit;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | data_bit;
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if(instance->decoder.decode_count_bit == 53) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
instance->generic.serial = (uint32_t)((instance->generic.data >> 20) & 0xFFFFFFFF);
|
||||
instance->generic.btn = (uint8_t)((instance->generic.data >> 16) & 0x0F);
|
||||
|
||||
uint16_t raw_count = (uint16_t)((instance->generic.data >> 4) & 0xFFF);
|
||||
instance->generic.cnt = ((raw_count >> 4) | (raw_count << 8)) & 0xFFF;
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
||||
|
||||
ret = pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
||||
|
||||
uint32_t crc = instance->generic.data & 0x0F;
|
||||
if(!flipper_format_write_uint32(flipper_format, "CRC", &crc, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t raw_count = (uint16_t)((instance->generic.data >> 4) & 0xFFF);
|
||||
if(!flipper_format_write_uint32(flipper_format, "RawCnt", &raw_count, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, kia_protocol_v2_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
|
||||
uint8_t crc = instance->generic.data & 0x0F;
|
||||
|
||||
bool crc_valid = crc == kia_v2_calculate_crc(instance->generic.data);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%013llX\r\n"
|
||||
"Sn:%08lX Btn:%X\r\n"
|
||||
"Cnt:%03lX CRC:%X - %s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.data,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
crc,
|
||||
crc_valid ? "OK" : "BAD");
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "kia_generic.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V2_NAME "Kia V2"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV2 SubGhzProtocolDecoderKiaV2;
|
||||
typedef struct SubGhzProtocolEncoderKiaV2 SubGhzProtocolEncoderKiaV2;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v2_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v2_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v2;
|
||||
|
||||
void* kia_protocol_decoder_v2_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v2_free(void* context);
|
||||
void kia_protocol_decoder_v2_reset(void* context);
|
||||
void kia_protocol_decoder_v2_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v2_get_string(void* context, FuriString* output);
|
||||
|
||||
void* kia_protocol_encoder_v2_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -0,0 +1,856 @@
|
||||
#include "kia_v3_v4.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "keeloq_common.h"
|
||||
#include "keys.h"
|
||||
#include "protocols_common.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define TAG "KiaV3V4"
|
||||
|
||||
static const char* kia_version_names[] = {"Kia V4", "Kia V3"};
|
||||
|
||||
#define KIA_V3_V4_PREAMBLE_PAIRS 12U
|
||||
#define KIA_V3_V4_BIT_COUNT 64U
|
||||
#define KIA_V3_V4_CRC_BIT_COUNT 4U
|
||||
#define KIA_V3_V4_CRC_SWEEP_COUNT 16U
|
||||
#define KIA_V3_V4_SYNC_DURATION 1200U
|
||||
#define KIA_V3_V4_END_MARKER_US 800U
|
||||
#define KIA_V3_V4_DEFAULT_REPEAT KIA_V3_V4_CRC_SWEEP_COUNT
|
||||
|
||||
#define KIA_V3_V4_DATA_OFFSET ((KIA_V3_V4_PREAMBLE_PAIRS * 2U) + 2U) // 26
|
||||
#define KIA_V3_V4_CRC_OFFSET (KIA_V3_V4_DATA_OFFSET + (KIA_V3_V4_BIT_COUNT * 2U))
|
||||
#define KIA_V3_V4_END_OFFSET (KIA_V3_V4_CRC_OFFSET + (KIA_V3_V4_CRC_BIT_COUNT * 2U))
|
||||
#define KIA_V3_V4_BURST_ENTRIES (KIA_V3_V4_END_OFFSET + 2U)
|
||||
|
||||
#define KIA_V3_V4_UPLOAD_CAPACITY KIA_V3_V4_BURST_ENTRIES
|
||||
|
||||
_Static_assert(
|
||||
KIA_V3_V4_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V3_V4_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v3_v4_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 68,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV3V4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
uint8_t raw_bits[32];
|
||||
uint16_t raw_bit_count;
|
||||
bool is_v3_sync;
|
||||
|
||||
uint32_t encrypted;
|
||||
uint32_t decrypted;
|
||||
uint8_t crc;
|
||||
uint8_t version;
|
||||
} SubGhzProtocolDecoderKiaV3V4;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderKiaV3V4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
uint8_t version;
|
||||
|
||||
uint32_t encrypted;
|
||||
uint32_t decrypted;
|
||||
|
||||
uint8_t crc_iter;
|
||||
uint8_t bursts_sent;
|
||||
} SubGhzProtocolEncoderKiaV3V4;
|
||||
|
||||
typedef enum {
|
||||
KiaV3V4DecoderStepReset = 0,
|
||||
KiaV3V4DecoderStepCheckPreamble,
|
||||
KiaV3V4DecoderStepCollectRawBits,
|
||||
} KiaV3V4DecoderStep;
|
||||
|
||||
static void kia_v3_v4_add_raw_bit(SubGhzProtocolDecoderKiaV3V4* instance, bool bit) {
|
||||
if(instance->raw_bit_count < 256) {
|
||||
uint16_t byte_idx = instance->raw_bit_count / 8;
|
||||
uint8_t bit_idx = 7 - (instance->raw_bit_count % 8);
|
||||
if(bit) {
|
||||
instance->raw_bits[byte_idx] |= (1 << bit_idx);
|
||||
} else {
|
||||
instance->raw_bits[byte_idx] &= ~(1 << bit_idx);
|
||||
}
|
||||
instance->raw_bit_count++;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static inline void kia_v3_v4_emit_bit_pwm(
|
||||
LevelDuration* upload,
|
||||
size_t* idx,
|
||||
bool bit,
|
||||
bool v4) {
|
||||
const uint32_t te_short = kia_protocol_v3_v4_const.te_short;
|
||||
const uint32_t te_long = kia_protocol_v3_v4_const.te_long;
|
||||
const uint32_t first_us = bit ? te_short : te_long;
|
||||
const uint32_t second_us = bit ? te_long : te_short;
|
||||
|
||||
if(v4) {
|
||||
upload[(*idx)++] = level_duration_make(false, (int32_t)first_us);
|
||||
upload[(*idx)++] = level_duration_make(true, (int32_t)second_us);
|
||||
} else {
|
||||
upload[(*idx)++] = level_duration_make(true, (int32_t)first_us);
|
||||
upload[(*idx)++] = level_duration_make(false, (int32_t)second_us);
|
||||
}
|
||||
}
|
||||
static uint64_t kia_v3_v4_build_tx_bitstream(SubGhzProtocolEncoderKiaV3V4* instance) {
|
||||
const uint32_t serial_btn = (instance->serial & 0x0FFFFFFFU) |
|
||||
((uint32_t)(instance->btn & 0x0FU) << 28);
|
||||
const uint64_t key = ((uint64_t)serial_btn << 32) | (uint64_t)instance->encrypted;
|
||||
return subghz_protocol_blocks_reverse_key(key, 64);
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
|
||||
if(instance->raw_bit_count < 68) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* b = instance->raw_bits;
|
||||
|
||||
if(instance->is_v3_sync) {
|
||||
uint16_t num_bytes = (instance->raw_bit_count + 7) / 8;
|
||||
for(uint16_t i = 0; i < num_bytes; i++) {
|
||||
b[i] = ~b[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t crc = (b[8] >> 4) & 0x0F;
|
||||
|
||||
uint32_t encrypted =
|
||||
((uint32_t)pp_reverse_bits8(b[3]) << 24) | ((uint32_t)pp_reverse_bits8(b[2]) << 16) |
|
||||
((uint32_t)pp_reverse_bits8(b[1]) << 8) | (uint32_t)pp_reverse_bits8(b[0]);
|
||||
|
||||
uint32_t serial = ((uint32_t)pp_reverse_bits8(b[7] & 0xF0) << 24) |
|
||||
((uint32_t)pp_reverse_bits8(b[6]) << 16) |
|
||||
((uint32_t)pp_reverse_bits8(b[5]) << 8) | (uint32_t)pp_reverse_bits8(b[4]);
|
||||
|
||||
uint8_t btn = (pp_reverse_bits8(b[7]) & 0xF0) >> 4;
|
||||
uint8_t our_serial_lsb = serial & 0xFF;
|
||||
|
||||
uint32_t decrypted = subghz_protocol_keeloq_common_decrypt(encrypted, get_kia_mf_key());
|
||||
uint8_t dec_btn = (decrypted >> 28) & 0x0F;
|
||||
uint8_t dec_serial_lsb = (decrypted >> 16) & 0xFF;
|
||||
|
||||
if(dec_btn != btn || dec_serial_lsb != our_serial_lsb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encrypted = encrypted;
|
||||
instance->decrypted = decrypted;
|
||||
instance->crc = crc;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = btn;
|
||||
instance->generic.cnt = decrypted & 0xFFFF;
|
||||
instance->version = instance->is_v3_sync ? 1 : 0;
|
||||
|
||||
uint64_t key_data = ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) | ((uint64_t)b[2] << 40) |
|
||||
((uint64_t)b[3] << 32) | ((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
|
||||
((uint64_t)b[6] << 8) | (uint64_t)b[7];
|
||||
instance->generic.data = key_data;
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v3_v4_decoder = {
|
||||
.alloc = kia_protocol_decoder_v3_v4_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = kia_protocol_decoder_v3_v4_feed,
|
||||
.reset = kia_protocol_decoder_v3_v4_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = kia_protocol_decoder_v3_v4_serialize,
|
||||
.deserialize = kia_protocol_decoder_v3_v4_deserialize,
|
||||
.get_string = kia_protocol_decoder_v3_v4_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v3_v4_encoder = {
|
||||
.alloc = kia_protocol_encoder_v3_v4_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = kia_protocol_encoder_v3_v4_deserialize,
|
||||
.stop = kia_protocol_encoder_v3_v4_stop,
|
||||
.yield = kia_protocol_encoder_v3_v4_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v3_v4_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v3_v4 = {
|
||||
.name = KIA_PROTOCOL_V3_V4_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &kia_protocol_v3_v4_decoder,
|
||||
.encoder = &kia_protocol_v3_v4_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
|
||||
furi_check(instance);
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v3_v4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
instance->version = 0;
|
||||
instance->crc_iter = 0;
|
||||
instance->bursts_sent = 0;
|
||||
|
||||
pp_encoder_buffer_ensure(instance, KIA_V3_V4_UPLOAD_CAPACITY);
|
||||
instance->encoder.repeat = (int32_t)KIA_V3_V4_DEFAULT_REPEAT;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
FURI_LOG_I(TAG, "Encoder allocated at %p", instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static void kia_protocol_encoder_v3_v4_build_packet(
|
||||
SubGhzProtocolEncoderKiaV3V4* instance,
|
||||
uint8_t* raw_bytes) {
|
||||
uint32_t plaintext = (uint32_t)(instance->cnt & 0xFFFFU) |
|
||||
((uint32_t)(instance->serial & 0x3FFU) << 16) |
|
||||
((uint32_t)(instance->btn & 0x0FU) << 28);
|
||||
|
||||
instance->decrypted = plaintext;
|
||||
|
||||
uint32_t encrypted = subghz_protocol_keeloq_common_encrypt(plaintext, get_kia_mf_key());
|
||||
instance->encrypted = encrypted;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encrypt: plain=0x%08lX -> enc=0x%08lX",
|
||||
(unsigned long)plaintext,
|
||||
(unsigned long)encrypted);
|
||||
|
||||
raw_bytes[0] = pp_reverse_bits8((encrypted >> 0) & 0xFF);
|
||||
raw_bytes[1] = pp_reverse_bits8((encrypted >> 8) & 0xFF);
|
||||
raw_bytes[2] = pp_reverse_bits8((encrypted >> 16) & 0xFF);
|
||||
raw_bytes[3] = pp_reverse_bits8((encrypted >> 24) & 0xFF);
|
||||
|
||||
uint32_t serial_btn = (instance->serial & 0x0FFFFFFFU) |
|
||||
((uint32_t)(instance->btn & 0x0F) << 28);
|
||||
raw_bytes[4] = pp_reverse_bits8((serial_btn >> 0) & 0xFF);
|
||||
raw_bytes[5] = pp_reverse_bits8((serial_btn >> 8) & 0xFF);
|
||||
raw_bytes[6] = pp_reverse_bits8((serial_btn >> 16) & 0xFF);
|
||||
raw_bytes[7] = pp_reverse_bits8((serial_btn >> 24) & 0xFF);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"TX raw: %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
raw_bytes[0],
|
||||
raw_bytes[1],
|
||||
raw_bytes[2],
|
||||
raw_bytes[3],
|
||||
raw_bytes[4],
|
||||
raw_bytes[5],
|
||||
raw_bytes[6],
|
||||
raw_bytes[7]);
|
||||
|
||||
instance->generic.data = ((uint64_t)raw_bytes[0] << 56) | ((uint64_t)raw_bytes[1] << 48) |
|
||||
((uint64_t)raw_bytes[2] << 40) | ((uint64_t)raw_bytes[3] << 32) |
|
||||
((uint64_t)raw_bytes[4] << 24) | ((uint64_t)raw_bytes[5] << 16) |
|
||||
((uint64_t)raw_bytes[6] << 8) | (uint64_t)raw_bytes[7];
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Packet built: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X",
|
||||
(unsigned long)instance->serial,
|
||||
instance->btn,
|
||||
instance->cnt);
|
||||
}
|
||||
static void kia_protocol_encoder_v3_v4_patch_crc(SubGhzProtocolEncoderKiaV3V4* instance) {
|
||||
if(!instance || !instance->encoder.upload) return;
|
||||
const bool v4 = (instance->version == 0);
|
||||
const uint8_t crc = instance->crc_iter & 0x0FU;
|
||||
size_t idx = KIA_V3_V4_CRC_OFFSET;
|
||||
for(int b = 3; b >= 0; b--) {
|
||||
const bool bit = (crc >> b) & 1U;
|
||||
kia_v3_v4_emit_bit_pwm(instance->encoder.upload, &idx, bit, v4);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static void kia_protocol_encoder_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
uint8_t raw_bytes[8];
|
||||
kia_protocol_encoder_v3_v4_build_packet(instance, raw_bytes);
|
||||
|
||||
const bool v4 = (instance->version == 0);
|
||||
const uint64_t tx_key = kia_v3_v4_build_tx_bitstream(instance);
|
||||
|
||||
size_t idx = 0;
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
const uint32_t te_short = kia_protocol_v3_v4_const.te_short;
|
||||
|
||||
for(uint32_t i = 0; i < KIA_V3_V4_PREAMBLE_PAIRS; i++) {
|
||||
if(v4) {
|
||||
upload[idx++] = level_duration_make(false, (int32_t)te_short);
|
||||
upload[idx++] = level_duration_make(true, (int32_t)te_short);
|
||||
} else {
|
||||
upload[idx++] = level_duration_make(true, (int32_t)te_short);
|
||||
upload[idx++] = level_duration_make(false, (int32_t)te_short);
|
||||
}
|
||||
}
|
||||
|
||||
if(v4) {
|
||||
upload[idx++] = level_duration_make(false, (int32_t)te_short);
|
||||
upload[idx++] = level_duration_make(true, (int32_t)KIA_V3_V4_SYNC_DURATION);
|
||||
} else {
|
||||
upload[idx++] = level_duration_make(true, (int32_t)te_short);
|
||||
upload[idx++] = level_duration_make(false, (int32_t)KIA_V3_V4_SYNC_DURATION);
|
||||
}
|
||||
|
||||
for(int i = 63; i >= 0; i--) {
|
||||
const bool bit = (tx_key >> i) & 1ULL;
|
||||
kia_v3_v4_emit_bit_pwm(upload, &idx, bit, v4);
|
||||
}
|
||||
|
||||
const uint8_t crc = instance->crc_iter & 0x0FU;
|
||||
for(int b = 3; b >= 0; b--) {
|
||||
const bool bit = (crc >> b) & 1U;
|
||||
kia_v3_v4_emit_bit_pwm(upload, &idx, bit, v4);
|
||||
}
|
||||
|
||||
if(v4) {
|
||||
upload[idx++] = level_duration_make(false, (int32_t)KIA_V3_V4_END_MARKER_US);
|
||||
upload[idx++] = level_duration_make(true, (int32_t)KIA_V3_V4_END_MARKER_US);
|
||||
} else {
|
||||
upload[idx++] = level_duration_make(true, (int32_t)KIA_V3_V4_END_MARKER_US);
|
||||
upload[idx++] = level_duration_make(false, (int32_t)KIA_V3_V4_END_MARKER_US);
|
||||
}
|
||||
|
||||
furi_check(idx == KIA_V3_V4_BURST_ENTRIES);
|
||||
instance->encoder.size_upload = idx;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Upload built: size=%zu %s enc=0x%08lX tx=0x%016llX crc=0x%X",
|
||||
instance->encoder.size_upload,
|
||||
v4 ? "V4" : "V3",
|
||||
(unsigned long)instance->encrypted,
|
||||
(unsigned long long)tx_key,
|
||||
crc);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
//instance->encoder.repeat = 40;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
do {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!flipper_format_read_string(flipper_format, FF_PROTOCOL, temp_str)) {
|
||||
FURI_LOG_E(TAG, "Missing Protocol");
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
// Accept "Kia V3/V4", "Kia V3", or "Kia V4"
|
||||
const char* proto_str = furi_string_get_cstr(temp_str);
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name) &&
|
||||
strcmp(proto_str, "Kia V3") != 0 && strcmp(proto_str, "Kia V4") != 0) {
|
||||
FURI_LOG_E(TAG, "Wrong protocol %s", proto_str);
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set version based on protocol name if specific
|
||||
bool version_from_protocol_name = false;
|
||||
|
||||
if(strcmp(proto_str, "Kia V3") == 0) {
|
||||
instance->version = 1;
|
||||
version_from_protocol_name = true;
|
||||
FURI_LOG_I(TAG, "Protocol name indicates V3");
|
||||
} else if(strcmp(proto_str, "Kia V4") == 0) {
|
||||
instance->version = 0;
|
||||
version_from_protocol_name = true;
|
||||
FURI_LOG_I(TAG, "Protocol name indicates V4");
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
uint32_t bits = 0;
|
||||
if(pp_encoder_read_bit(flipper_format, NULL, 0, &bits) != SubGhzProtocolStatusOk) break;
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
if(!pp_flipper_read_hex_u64(flipper_format, FF_KEY, &instance->generic.data)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) break;
|
||||
instance->serial = serial;
|
||||
instance->btn = (uint8_t)btn;
|
||||
instance->cnt = (uint16_t)cnt;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->btn;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
|
||||
// Read version - ONLY use file version if protocol name didn't specify one
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t version_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &version_temp, 1)) {
|
||||
if(!version_from_protocol_name) {
|
||||
instance->version = (uint8_t)version_temp;
|
||||
}
|
||||
} else if(!version_from_protocol_name) {
|
||||
instance->version = 0;
|
||||
}
|
||||
|
||||
instance->encoder.repeat =
|
||||
(int32_t)pp_encoder_read_repeat(flipper_format, KIA_V3_V4_DEFAULT_REPEAT);
|
||||
|
||||
instance->crc_iter = 0;
|
||||
instance->bursts_sent = 0;
|
||||
|
||||
kia_protocol_encoder_v3_v4_get_upload(instance);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder initialized: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X, Version=%s",
|
||||
(unsigned long)instance->serial,
|
||||
instance->btn,
|
||||
instance->cnt,
|
||||
instance->version == 0 ? "V4" : "V3");
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v3_v4_stop(void* context) {
|
||||
if(!context) return;
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
LevelDuration kia_protocol_encoder_v3_v4_yield(void* context) {
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
|
||||
if(!instance || !instance->encoder.upload || instance->encoder.repeat == 0 ||
|
||||
!instance->encoder.is_running) {
|
||||
if(instance) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder yield stopped: repeat=%u, is_running=%d",
|
||||
instance->encoder.repeat,
|
||||
instance->encoder.is_running);
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
if(instance->encoder.front >= instance->encoder.size_upload) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Encoder front out of bounds: %zu >= %zu",
|
||||
instance->encoder.front,
|
||||
instance->encoder.size_upload);
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->crc_iter = (uint8_t)((instance->crc_iter + 1U) & 0x0FU);
|
||||
kia_protocol_encoder_v3_v4_patch_crc(instance);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat--;
|
||||
if(instance->bursts_sent < KIA_V3_V4_CRC_SWEEP_COUNT) {
|
||||
instance->bursts_sent++;
|
||||
}
|
||||
if(instance->encoder.repeat == 0) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"CRC BF: %u/%u bursts transmitted (~%u ms)",
|
||||
instance->bursts_sent,
|
||||
KIA_V3_V4_CRC_SWEEP_COUNT,
|
||||
(unsigned)(instance->bursts_sent * 94U));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->btn = button & 0x0F;
|
||||
instance->generic.btn = instance->btn;
|
||||
kia_protocol_encoder_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Button set to 0x%X", instance->btn);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->cnt = counter;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
kia_protocol_encoder_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Counter set to 0x%04X", instance->cnt);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
void kia_protocol_encoder_v3_v4_increment_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->cnt++;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
kia_protocol_encoder_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Counter incremented to 0x%04X", instance->cnt);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
return instance->cnt;
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
uint8_t kia_protocol_encoder_v3_v4_get_button(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
return instance->btn;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* kia_protocol_decoder_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV3V4));
|
||||
furi_check(instance);
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v3_v4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v3_v4_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->crc = 0;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v3_v4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV3V4DecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta)) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV3V4DecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else if(duration > 1000 && duration < 1500) {
|
||||
if(instance->header_count >= 8) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = false;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(instance->header_count >= 8) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = true;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if(duration > 1500) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV3V4DecoderStepCollectRawBits:
|
||||
if(level) {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, false);
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_long) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, true);
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
} else if(duration > 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v3_v4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
// Write frequency
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_FREQUENCY, &preset->frequency, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write preset
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, FF_PRESET, furi_string_get_cstr(preset->name))) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write version-specific protocol name instead of generic "Kia V3/V4"
|
||||
const char* version_name = (instance->version == 0) ? "Kia V4" : "Kia V3";
|
||||
if(!flipper_format_write_string_cstr(flipper_format, FF_PROTOCOL, version_name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write bit count
|
||||
uint32_t bits = instance->generic.data_count_bit;
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_BIT, &bits, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write key
|
||||
char key_str[20];
|
||||
snprintf(key_str, sizeof(key_str), "%016llX", (unsigned long long)instance->generic.data);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, FF_KEY, key_str)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write all fields needed by encoder
|
||||
if(pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0) != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write protocol-specific fields
|
||||
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp = instance->version;
|
||||
if(!flipper_format_write_uint32(flipper_format, "KIAVersion", &temp, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
temp = instance->crc;
|
||||
if(!flipper_format_write_uint32(flipper_format, "CRC", &temp, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize_check_count_bit(&instance->generic, flipper_format, 64);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = 0;
|
||||
|
||||
if(flipper_format_read_uint32(flipper_format, "Encrypted", &temp, 1)) {
|
||||
instance->encrypted = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Decrypted", &temp, 1)) {
|
||||
instance->decrypted = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &temp, 1)) {
|
||||
instance->version = (uint8_t)temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "CRC", &temp, 1)) {
|
||||
instance->crc = (uint8_t)temp;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t compute_yek(uint64_t key) {
|
||||
uint64_t yek = 0;
|
||||
for(int i = 0; i < 64; i++) {
|
||||
yek |= ((key >> i) & 1) << (63 - i);
|
||||
}
|
||||
return yek;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v3_v4_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
uint64_t yek = compute_yek(instance->generic.data);
|
||||
uint32_t key_hi = (uint32_t)(instance->generic.data >> 32);
|
||||
uint32_t key_lo = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
|
||||
uint32_t yek_hi = (uint32_t)(yek >> 32);
|
||||
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Serial:%07lX Btn:%01X\r\n"
|
||||
"Cnt:%04lX CRC:%01X\r\n",
|
||||
kia_version_names[instance->version],
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
instance->crc);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "kia_generic.h"
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V3_V4_NAME "Kia V3/V4"
|
||||
|
||||
extern const SubGhzProtocol kia_protocol_v3_v4;
|
||||
|
||||
// Decoder functions
|
||||
void* kia_protocol_decoder_v3_v4_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v3_v4_free(void* context);
|
||||
void kia_protocol_decoder_v3_v4_reset(void* context);
|
||||
void kia_protocol_decoder_v3_v4_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v3_v4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v3_v4_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* kia_protocol_encoder_v3_v4_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v3_v4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_encoder_v3_v4_stop(void* context);
|
||||
LevelDuration kia_protocol_encoder_v3_v4_yield(void* context);
|
||||
|
||||
// Encoder helper functions for UI
|
||||
void kia_protocol_encoder_v3_v4_set_button(void* context, uint8_t button);
|
||||
void kia_protocol_encoder_v3_v4_set_counter(void* context, uint16_t counter);
|
||||
void kia_protocol_encoder_v3_v4_increment_counter(void* context);
|
||||
uint16_t kia_protocol_encoder_v3_v4_get_counter(void* context);
|
||||
uint8_t kia_protocol_encoder_v3_v4_get_button(void* context);
|
||||
@@ -0,0 +1,661 @@
|
||||
#include "kia_v5.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "protocols_common.h"
|
||||
#include "keys.h"
|
||||
|
||||
#define TAG "KiaV5"
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v5_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
static void build_keystore_from_mfkey(uint8_t* result) {
|
||||
uint64_t mfkey = get_kia_v5_key();
|
||||
for(int i = 0; i < 8; i++) {
|
||||
result[i] = (uint8_t)((mfkey >> ((7 - i) * 8)) & 0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t keystore_bytes[8] = {0};
|
||||
|
||||
static uint16_t mixer_decode(uint32_t encrypted) {
|
||||
uint8_t s0 = (encrypted & 0xFF);
|
||||
uint8_t s1 = (encrypted >> 8) & 0xFF;
|
||||
uint8_t s2 = (encrypted >> 16) & 0xFF;
|
||||
uint8_t s3 = (encrypted >> 24) & 0xFF;
|
||||
|
||||
// Prepare key
|
||||
build_keystore_from_mfkey(keystore_bytes);
|
||||
|
||||
int round_index = 1;
|
||||
for(size_t i = 0; i < 18; i++) {
|
||||
uint8_t r = keystore_bytes[round_index] & 0xFF;
|
||||
int steps = 8;
|
||||
while(steps > 0) {
|
||||
uint8_t base;
|
||||
if((s3 & 0x40) == 0) {
|
||||
base = (s3 & 0x02) == 0 ? 0x74 : 0x2E;
|
||||
} else {
|
||||
base = (s3 & 0x02) == 0 ? 0x3A : 0x5C;
|
||||
}
|
||||
|
||||
if(s2 & 0x08) {
|
||||
base = (((base >> 4) & 0x0F) | ((base & 0x0F) << 4)) & 0xFF;
|
||||
}
|
||||
if(s1 & 0x01) {
|
||||
base = ((base & 0x3F) << 2) & 0xFF;
|
||||
}
|
||||
if(s0 & 0x01) {
|
||||
base = (base << 1) & 0xFF;
|
||||
}
|
||||
|
||||
uint8_t temp = (s3 ^ s1) & 0xFF;
|
||||
s3 = ((s3 & 0x7F) << 1) & 0xFF;
|
||||
if(s2 & 0x80) {
|
||||
s3 |= 0x01;
|
||||
}
|
||||
s2 = ((s2 & 0x7F) << 1) & 0xFF;
|
||||
if(s1 & 0x80) {
|
||||
s2 |= 0x01;
|
||||
}
|
||||
s1 = ((s1 & 0x7F) << 1) & 0xFF;
|
||||
if(s0 & 0x80) {
|
||||
s1 |= 0x01;
|
||||
}
|
||||
s0 = ((s0 & 0x7F) << 1) & 0xFF;
|
||||
|
||||
uint8_t chk = (base ^ (r ^ temp)) & 0xFF;
|
||||
if(chk & 0x80) {
|
||||
s0 |= 0x01;
|
||||
}
|
||||
r = ((r & 0x7F) << 1) & 0xFF;
|
||||
steps--;
|
||||
}
|
||||
round_index = (round_index - 1) & 0x7;
|
||||
}
|
||||
return (s0 + (s1 << 8)) & 0xFFFF;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint32_t mixer_encode(uint32_t serial, uint16_t counter, uint8_t button) {
|
||||
build_keystore_from_mfkey(keystore_bytes);
|
||||
|
||||
uint8_t state_a = (uint8_t)(((serial >> 8) & 0x0FU) | ((button & 0x0FU) << 4));
|
||||
uint8_t state_b = (uint8_t)((counter >> 8) & 0xFFU);
|
||||
uint8_t state_c = (uint8_t)(serial & 0xFFU);
|
||||
uint8_t state_d = (uint8_t)(counter & 0xFFU);
|
||||
|
||||
int ks_idx = 0;
|
||||
for(int round_i = 0; round_i < 18; round_i++) {
|
||||
uint8_t r = keystore_bytes[ks_idx] & 0xFFU;
|
||||
ks_idx = (ks_idx + 1) & 0x07;
|
||||
|
||||
uint8_t running_d = state_d;
|
||||
for(int step = 0; step < 8; step++) {
|
||||
uint8_t base;
|
||||
if((state_a & 0x80U) == 0) {
|
||||
base = (state_a & 0x04U) == 0 ? 0x74U : 0x2EU;
|
||||
} else {
|
||||
base = (state_a & 0x04U) == 0 ? 0x3AU : 0x5CU;
|
||||
}
|
||||
|
||||
if(state_c & 0x10U) {
|
||||
base = (uint8_t)(((base >> 4) & 0x0FU) | ((base & 0x0FU) << 4));
|
||||
}
|
||||
if(state_b & 0x02U) {
|
||||
base = (uint8_t)((base & 0x3FU) << 2);
|
||||
}
|
||||
|
||||
uint8_t base_final = base;
|
||||
if(running_d & 0x02U) {
|
||||
base_final = (uint8_t)((base & 0x7FU) << 1);
|
||||
}
|
||||
|
||||
const bool carry_b = (state_b & 0x01U) != 0;
|
||||
const bool carry_c = (state_c & 0x01U) != 0;
|
||||
const bool carry_a = (state_a & 0x01U) != 0;
|
||||
|
||||
uint8_t new_d = (uint8_t)(running_d >> 1);
|
||||
if(carry_b) new_d |= 0x80U;
|
||||
|
||||
running_d ^= state_c;
|
||||
|
||||
state_b = (uint8_t)(state_b >> 1);
|
||||
if(carry_c) state_b |= 0x80U;
|
||||
|
||||
state_c = (uint8_t)(state_c >> 1);
|
||||
if(carry_a) state_c |= 0x80U;
|
||||
|
||||
const uint8_t feedback = (uint8_t)(((running_d ^ r) << 7) ^ base_final);
|
||||
state_a = (uint8_t)(state_a >> 1);
|
||||
if(feedback & 0x80U) state_a |= 0x80U;
|
||||
|
||||
r = (uint8_t)(r >> 1);
|
||||
running_d = new_d;
|
||||
}
|
||||
state_d = running_d;
|
||||
}
|
||||
|
||||
return ((uint32_t)state_a << 24) | ((uint32_t)state_c << 16) | ((uint32_t)state_b << 8) |
|
||||
(uint32_t)state_d;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV5 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint64_t decoded_data;
|
||||
uint64_t saved_key;
|
||||
uint8_t bit_count;
|
||||
uint64_t yek;
|
||||
uint8_t crc;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV5 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t replay_data;
|
||||
uint8_t replay_crc;
|
||||
};
|
||||
|
||||
#define KIA_V5_PREAMBLE_PAIRS 200U
|
||||
#define KIA_V5_SYNC_ENTRIES 4U
|
||||
#define KIA_V5_DATA_BITS 64U
|
||||
#define KIA_V5_CRC_BITS 3U
|
||||
#define KIA_V5_END_ENTRIES 2U
|
||||
#define KIA_V5_UPLOAD_CAPACITY \
|
||||
(KIA_V5_PREAMBLE_PAIRS * 2U + KIA_V5_SYNC_ENTRIES + \
|
||||
(KIA_V5_DATA_BITS + KIA_V5_CRC_BITS) * 2U + KIA_V5_END_ENTRIES)
|
||||
_Static_assert(
|
||||
KIA_V5_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V5_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
typedef enum {
|
||||
KiaV5DecoderStepReset = 0,
|
||||
KiaV5DecoderStepCheckPreamble,
|
||||
KiaV5DecoderStepData,
|
||||
} KiaV5DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v5_decoder = {
|
||||
.alloc = kia_protocol_decoder_v5_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = kia_protocol_decoder_v5_feed,
|
||||
.reset = kia_protocol_decoder_v5_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = kia_protocol_decoder_v5_serialize,
|
||||
.deserialize = kia_protocol_decoder_v5_deserialize,
|
||||
.get_string = kia_protocol_decoder_v5_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v5_encoder = {
|
||||
.alloc = kia_protocol_encoder_v5_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = kia_protocol_encoder_v5_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v5_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v5 = {
|
||||
.name = KIA_PROTOCOL_V5_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
| SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send
|
||||
#endif
|
||||
,
|
||||
.decoder = &kia_protocol_v5_decoder,
|
||||
.encoder = &kia_protocol_v5_encoder,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static uint8_t kia_v5_calculate_crc(uint64_t data) {
|
||||
uint8_t crc = 0;
|
||||
for(int i = 63; i >= 0; i--) {
|
||||
const uint8_t bit = (data >> i) & 1U;
|
||||
const uint8_t shifted_out = (crc >> 1U) & 1U;
|
||||
crc = (uint8_t)(((crc & 1U) << 1U) | bit);
|
||||
if(shifted_out) {
|
||||
crc ^= 3U;
|
||||
}
|
||||
}
|
||||
return (uint8_t)(crc & 3U);
|
||||
}
|
||||
|
||||
void* kia_protocol_encoder_v5_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
|
||||
furi_check(instance);
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v5;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 6;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
static size_t kia_v5_emit_manchester_bit(LevelDuration* up, size_t i, size_t cap, bool bit_value) {
|
||||
const uint32_t te = kia_protocol_v5_const.te_short;
|
||||
if(bit_value) {
|
||||
i = pp_emit(up, i, cap, false, te);
|
||||
i = pp_emit(up, i, cap, true, te);
|
||||
} else {
|
||||
i = pp_emit(up, i, cap, true, te);
|
||||
i = pp_emit(up, i, cap, false, te);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void kia_protocol_encoder_v5_get_upload(SubGhzProtocolEncoderKiaV5* instance) {
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
const size_t cap = KIA_V5_UPLOAD_CAPACITY;
|
||||
const uint32_t te_short = kia_protocol_v5_const.te_short;
|
||||
const uint32_t te_long = kia_protocol_v5_const.te_long;
|
||||
size_t i = 0;
|
||||
|
||||
for(size_t p = 0; p < KIA_V5_PREAMBLE_PAIRS; p++) {
|
||||
i = pp_emit(upload, i, cap, true, te_short);
|
||||
i = pp_emit(upload, i, cap, false, te_short);
|
||||
}
|
||||
|
||||
i = pp_emit(upload, i, cap, false, te_short);
|
||||
i = pp_emit(upload, i, cap, true, te_long);
|
||||
i = pp_emit(upload, i, cap, false, te_short);
|
||||
i = pp_emit(upload, i, cap, true, te_short);
|
||||
|
||||
for(int b = (int)KIA_V5_DATA_BITS - 1; b >= 0; b--) {
|
||||
const bool bit_value = ((instance->replay_data >> b) & 1ULL) != 0ULL;
|
||||
i = kia_v5_emit_manchester_bit(upload, i, cap, bit_value);
|
||||
}
|
||||
|
||||
i = kia_v5_emit_manchester_bit(upload, i, cap, false);
|
||||
i = kia_v5_emit_manchester_bit(upload, i, cap, ((instance->replay_crc >> 1U) & 1U) != 0U);
|
||||
i = kia_v5_emit_manchester_bit(upload, i, cap, (instance->replay_crc & 1U) != 0U);
|
||||
|
||||
i = pp_emit(upload, i, cap, false, te_short);
|
||||
i = pp_emit(upload, i, cap, true, te_short);
|
||||
|
||||
instance->encoder.size_upload = i;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v5_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "V5 enc: protocol mismatch");
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "V5 enc: generic deserialize failed (%d)", status);
|
||||
return status;
|
||||
}
|
||||
if(instance->generic.data_count_bit < kia_protocol_v5_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(
|
||||
TAG, "V5 enc: bit count too low: %u", (unsigned)instance->generic.data_count_bit);
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
uint32_t serial_v = UINT32_MAX;
|
||||
uint32_t btn_v = UINT32_MAX;
|
||||
uint32_t cnt_v = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial_v, &btn_v, &cnt_v, NULL);
|
||||
|
||||
uint64_t yek_captured = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
const uint8_t b = (uint8_t)((instance->generic.data >> (i * 8)) & 0xFFU);
|
||||
yek_captured |= ((uint64_t)pp_reverse_bits8(b) << ((7 - i) * 8));
|
||||
}
|
||||
if(serial_v == UINT32_MAX) {
|
||||
serial_v = (uint32_t)((yek_captured >> 32) & 0x0FFFFFFFU);
|
||||
}
|
||||
if(btn_v == UINT32_MAX) {
|
||||
btn_v = (uint32_t)((yek_captured >> 60) & 0x0FU);
|
||||
}
|
||||
if(cnt_v == UINT32_MAX) {
|
||||
cnt_v = mixer_decode((uint32_t)(yek_captured & 0xFFFFFFFFU));
|
||||
}
|
||||
|
||||
serial_v &= 0x0FFFFFFFU;
|
||||
btn_v &= 0x0FU;
|
||||
cnt_v &= 0xFFFFU;
|
||||
|
||||
const uint32_t mixer = mixer_encode(serial_v, (uint16_t)cnt_v, (uint8_t)btn_v);
|
||||
const uint64_t yek_new =
|
||||
((uint64_t)btn_v << 60) | ((uint64_t)serial_v << 32) | (uint64_t)mixer;
|
||||
|
||||
uint64_t data_new = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
const uint8_t b = (uint8_t)((yek_new >> (i * 8)) & 0xFFU);
|
||||
data_new |= ((uint64_t)pp_reverse_bits8(b) << ((7 - i) * 8));
|
||||
}
|
||||
|
||||
instance->replay_data = data_new;
|
||||
instance->generic.data = data_new;
|
||||
instance->generic.serial = serial_v;
|
||||
instance->generic.btn = (uint8_t)btn_v;
|
||||
instance->generic.cnt = (uint16_t)cnt_v;
|
||||
instance->replay_crc = kia_v5_calculate_crc(instance->replay_data);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"V5 enc reencrypt: sn=%07lX btn=%X cnt=%04lX mixer=%08lX",
|
||||
(unsigned long)serial_v,
|
||||
(unsigned)btn_v,
|
||||
(unsigned long)cnt_v,
|
||||
(unsigned long)mixer);
|
||||
|
||||
instance->encoder.repeat = (int32_t)pp_encoder_read_repeat(flipper_format, 6);
|
||||
|
||||
pp_encoder_buffer_ensure(instance, KIA_V5_UPLOAD_CAPACITY);
|
||||
kia_protocol_encoder_v5_get_upload(instance);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"V5 enc ready: data=%08lX%08lX crc=%X repeat=%u size=%zu",
|
||||
(uint32_t)(instance->replay_data >> 32),
|
||||
(uint32_t)(instance->replay_data & 0xFFFFFFFFULL),
|
||||
instance->replay_crc,
|
||||
(unsigned)instance->encoder.repeat,
|
||||
instance->encoder.size_upload);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"V5 enc preamble[0..3]: %s%lu %s%lu %s%lu %s%lu",
|
||||
level_duration_get_level(instance->encoder.upload[0]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[0]),
|
||||
level_duration_get_level(instance->encoder.upload[1]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[1]),
|
||||
level_duration_get_level(instance->encoder.upload[2]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[2]),
|
||||
level_duration_get_level(instance->encoder.upload[3]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[3]));
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"V5 enc sync[400..403]: %s%lu %s%lu %s%lu %s%lu",
|
||||
level_duration_get_level(instance->encoder.upload[400]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[400]),
|
||||
level_duration_get_level(instance->encoder.upload[401]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[401]),
|
||||
level_duration_get_level(instance->encoder.upload[402]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[402]),
|
||||
level_duration_get_level(instance->encoder.upload[403]) ? "H" : "L",
|
||||
(unsigned long)level_duration_get_duration(instance->encoder.upload[403]));
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static void kia_v5_add_bit(SubGhzProtocolDecoderKiaV5* instance, bool bit) {
|
||||
instance->decoded_data = (instance->decoded_data << 1) | (bit ? 1 : 0);
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
void* kia_protocol_decoder_v5_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolDecoderKiaV5* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV5));
|
||||
furi_check(instance);
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v5;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v5_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
instance->decoder.parser_step = KiaV5DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoded_data = 0;
|
||||
instance->saved_key = 0;
|
||||
instance->yek = 0;
|
||||
instance->crc = 0;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v5_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV5DecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta)) {
|
||||
instance->decoder.parser_step = KiaV5DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 1;
|
||||
instance->bit_count = 0;
|
||||
instance->decoded_data = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV5DecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, kia_protocol_v5_const.te_long) <
|
||||
kia_protocol_v5_const.te_delta) {
|
||||
if(instance->header_count > 40) {
|
||||
instance->decoder.parser_step = KiaV5DecoderStepData;
|
||||
instance->bit_count = 0;
|
||||
instance->decoded_data = 0;
|
||||
instance->saved_key = 0;
|
||||
instance->header_count = 0;
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV5DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if((DURATION_DIFF(duration, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, kia_protocol_v5_const.te_long) <
|
||||
kia_protocol_v5_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta)) {
|
||||
instance->header_count++;
|
||||
} else if(
|
||||
DURATION_DIFF(instance->decoder.te_last, kia_protocol_v5_const.te_long) <
|
||||
kia_protocol_v5_const.te_delta) {
|
||||
instance->header_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV5DecoderStepReset;
|
||||
}
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV5DecoderStepData: {
|
||||
ManchesterEvent event;
|
||||
|
||||
if(DURATION_DIFF(duration, kia_protocol_v5_const.te_short) <
|
||||
kia_protocol_v5_const.te_delta) {
|
||||
event = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v5_const.te_long) <
|
||||
kia_protocol_v5_const.te_delta) {
|
||||
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
} else {
|
||||
if(instance->bit_count >= kia_protocol_v5_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->saved_key;
|
||||
instance->generic.data_count_bit =
|
||||
(instance->bit_count > 67) ? 67 : instance->bit_count;
|
||||
|
||||
instance->crc = (uint8_t)(instance->decoded_data & 0x07);
|
||||
|
||||
instance->yek = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
uint8_t byte = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
uint8_t reversed = 0;
|
||||
for(int b = 0; b < 8; b++) {
|
||||
if(byte & (1 << b)) reversed |= (1 << (7 - b));
|
||||
}
|
||||
instance->yek |= ((uint64_t)reversed << ((7 - i) * 8));
|
||||
}
|
||||
|
||||
instance->generic.serial = (uint32_t)((instance->yek >> 32) & 0x0FFFFFFF);
|
||||
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
|
||||
|
||||
uint32_t encrypted = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
instance->generic.cnt = mixer_decode(encrypted);
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Key=%08lX%08lX Sn=%07lX Btn=%X Cnt=%04lX CRC=%X",
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
instance->crc);
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->decoder.parser_step = KiaV5DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
bool data_bit;
|
||||
if(instance->bit_count <= 66 &&
|
||||
manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
kia_v5_add_bit(instance, data_bit);
|
||||
|
||||
if(instance->bit_count == 64) {
|
||||
instance->saved_key = instance->decoded_data;
|
||||
instance->decoded_data = 0;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v5_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
// Save decoded fields
|
||||
pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
|
||||
uint32_t crc_temp = instance->crc;
|
||||
flipper_format_write_uint32(flipper_format, "CRC", &crc_temp, 1);
|
||||
|
||||
// Save raw bit data for exact reproduction (since V5 has complex bit reversal)
|
||||
uint32_t raw_high = (uint32_t)(instance->generic.data >> 32);
|
||||
uint32_t raw_low = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
|
||||
flipper_format_write_uint32(flipper_format, "DataHi", &raw_high, 1);
|
||||
flipper_format_write_uint32(flipper_format, "DataLo", &raw_low, 1);
|
||||
uint32_t yek_high = (uint32_t)(instance->yek >> 32);
|
||||
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
flipper_format_write_uint32(flipper_format, "YekHi", &yek_high, 1);
|
||||
flipper_format_write_uint32(flipper_format, "YekLo", &yek_low, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v5_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, kia_protocol_v5_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v5_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
|
||||
uint32_t code_found_hi = instance->generic.data >> 32;
|
||||
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
|
||||
uint32_t yek_hi = (uint32_t)(instance->yek >> 32);
|
||||
uint32_t yek_lo = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
|
||||
"CRC:%X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
code_found_hi,
|
||||
code_found_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
instance->crc);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "kia_generic.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V5_NAME "Kia V5"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV5 SubGhzProtocolDecoderKiaV5;
|
||||
typedef struct SubGhzProtocolEncoderKiaV5 SubGhzProtocolEncoderKiaV5;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v5_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v5_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v5;
|
||||
|
||||
void* kia_protocol_decoder_v5_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v5_free(void* context);
|
||||
void kia_protocol_decoder_v5_reset(void* context);
|
||||
void kia_protocol_decoder_v5_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v5_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v5_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v5_get_string(void* context, FuriString* output);
|
||||
|
||||
void* kia_protocol_encoder_v5_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v5_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -0,0 +1,989 @@
|
||||
#include "kia_v6.h"
|
||||
#include "../protopirate_app_i.h"
|
||||
#include "protocols_common.h"
|
||||
#include "keys.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "KiaV6"
|
||||
|
||||
#define KIA_V6_XOR_MASK_LOW 0x84AF25FB
|
||||
#define KIA_V6_XOR_MASK_HIGH 0x638766AB
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v6_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 144,
|
||||
};
|
||||
|
||||
static const uint8_t aes_sbox[256] = {
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab,
|
||||
0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4,
|
||||
0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71,
|
||||
0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
|
||||
0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6,
|
||||
0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb,
|
||||
0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45,
|
||||
0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
|
||||
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44,
|
||||
0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a,
|
||||
0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
|
||||
0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
|
||||
0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25,
|
||||
0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e,
|
||||
0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1,
|
||||
0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb,
|
||||
0x16};
|
||||
|
||||
static const uint8_t aes_sbox_inv[256] = {
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7,
|
||||
0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde,
|
||||
0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42,
|
||||
0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
|
||||
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c,
|
||||
0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15,
|
||||
0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7,
|
||||
0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
|
||||
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc,
|
||||
0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad,
|
||||
0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d,
|
||||
0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
|
||||
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8,
|
||||
0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51,
|
||||
0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0,
|
||||
0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c,
|
||||
0x7d};
|
||||
|
||||
static const uint8_t aes_rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV6 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
|
||||
uint32_t data_part1_low;
|
||||
uint32_t data_part1_high;
|
||||
|
||||
uint32_t stored_part1_low;
|
||||
uint32_t stored_part1_high;
|
||||
uint32_t stored_part2_low;
|
||||
uint32_t stored_part2_high;
|
||||
uint16_t data_part3;
|
||||
|
||||
uint8_t bit_count;
|
||||
uint8_t fx_field;
|
||||
uint8_t crc1_field;
|
||||
uint8_t crc2_field;
|
||||
bool keys_loaded;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV6 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint32_t stored_part1_low;
|
||||
uint32_t stored_part1_high;
|
||||
uint32_t stored_part2_low;
|
||||
uint32_t stored_part2_high;
|
||||
uint16_t data_part3;
|
||||
uint8_t fx_field;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV6DecoderStepReset = 0,
|
||||
KiaV6DecoderStepWaitFirstHigh,
|
||||
KiaV6DecoderStepCountPreamble,
|
||||
KiaV6DecoderStepWaitLongHigh,
|
||||
KiaV6DecoderStepData,
|
||||
} KiaV6DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v6_decoder = {
|
||||
.alloc = kia_protocol_decoder_v6_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = kia_protocol_decoder_v6_feed,
|
||||
.reset = kia_protocol_decoder_v6_reset,
|
||||
.get_hash_data = kia_protocol_decoder_v6_get_hash_data,
|
||||
.serialize = kia_protocol_decoder_v6_serialize,
|
||||
.deserialize = kia_protocol_decoder_v6_deserialize,
|
||||
.get_string = kia_protocol_decoder_v6_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v6_encoder = {
|
||||
.alloc = kia_protocol_encoder_v6_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = kia_protocol_encoder_v6_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v6_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v6 = {
|
||||
.name = KIA_PROTOCOL_V6_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load,
|
||||
.decoder = &kia_protocol_v6_decoder,
|
||||
.encoder = &kia_protocol_v6_encoder,
|
||||
};
|
||||
|
||||
#define kia_v6_crc8(data, len) subghz_protocol_blocks_crc8((data), (len), 0x07, 0xFF)
|
||||
|
||||
static uint8_t gf_mul2(uint8_t x) {
|
||||
return ((x >> 7) * 0x1b) ^ (x << 1);
|
||||
}
|
||||
|
||||
static void aes_subbytes_inv(uint8_t* state) {
|
||||
for(int row = 0; row < 4; row++) {
|
||||
for(int col = 0; col < 4; col++) {
|
||||
state[row + col * 4] = aes_sbox_inv[state[row + col * 4]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_shiftrows_inv(uint8_t* state) {
|
||||
uint8_t temp;
|
||||
|
||||
temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
static void aes_mixcolumns_inv(uint8_t* state) {
|
||||
uint8_t a, b, c, d;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
a = state[i * 4];
|
||||
b = state[i * 4 + 1];
|
||||
c = state[i * 4 + 2];
|
||||
d = state[i * 4 + 3];
|
||||
|
||||
uint8_t a2 = gf_mul2(a);
|
||||
uint8_t a4 = gf_mul2(a2);
|
||||
uint8_t a8 = gf_mul2(a4);
|
||||
uint8_t b2 = gf_mul2(b);
|
||||
uint8_t b4 = gf_mul2(b2);
|
||||
uint8_t b8 = gf_mul2(b4);
|
||||
uint8_t c2 = gf_mul2(c);
|
||||
uint8_t c4 = gf_mul2(c2);
|
||||
uint8_t c8 = gf_mul2(c4);
|
||||
uint8_t d2 = gf_mul2(d);
|
||||
uint8_t d4 = gf_mul2(d2);
|
||||
uint8_t d8 = gf_mul2(d4);
|
||||
|
||||
state[i * 4] = (a8 ^ a4 ^ a2) ^ (b8 ^ b2 ^ b) ^ (c8 ^ c4 ^ c) ^ (d8 ^ d);
|
||||
state[i * 4 + 1] = (a8 ^ a) ^ (b8 ^ b4 ^ b2) ^ (c8 ^ c2 ^ c) ^ (d8 ^ d4 ^ d);
|
||||
state[i * 4 + 2] = (a8 ^ a4 ^ a) ^ (b8 ^ b) ^ (c8 ^ c4 ^ c2) ^ (d8 ^ d2 ^ d);
|
||||
state[i * 4 + 3] = (a8 ^ a2 ^ a) ^ (b8 ^ b4 ^ b) ^ (c8 ^ c) ^ (d8 ^ d4 ^ d2);
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
|
||||
for(int col = 0; col < 4; col++) {
|
||||
state[col * 4] ^= round_key[col * 4];
|
||||
state[col * 4 + 1] ^= round_key[col * 4 + 1];
|
||||
state[col * 4 + 2] ^= round_key[col * 4 + 2];
|
||||
state[col * 4 + 3] ^= round_key[col * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void aes_subbytes(uint8_t* state) {
|
||||
for(int row = 0; row < 4; row++) {
|
||||
for(int col = 0; col < 4; col++) {
|
||||
state[row + col * 4] = aes_sbox[state[row + col * 4]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_shiftrows(uint8_t* state) {
|
||||
uint8_t temp;
|
||||
temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
temp = state[3];
|
||||
state[3] = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = temp;
|
||||
}
|
||||
|
||||
static void aes_mixcolumns(uint8_t* state) {
|
||||
uint8_t a, b, c, d;
|
||||
for(int i = 0; i < 4; i++) {
|
||||
a = state[i * 4];
|
||||
b = state[i * 4 + 1];
|
||||
c = state[i * 4 + 2];
|
||||
d = state[i * 4 + 3];
|
||||
state[i * 4] = gf_mul2(a) ^ gf_mul2(b) ^ b ^ c ^ d;
|
||||
state[i * 4 + 1] = a ^ gf_mul2(b) ^ gf_mul2(c) ^ c ^ d;
|
||||
state[i * 4 + 2] = a ^ b ^ gf_mul2(c) ^ gf_mul2(d) ^ d;
|
||||
state[i * 4 + 3] = gf_mul2(a) ^ a ^ b ^ c ^ gf_mul2(d);
|
||||
}
|
||||
}
|
||||
|
||||
static void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) {
|
||||
uint8_t state[16];
|
||||
memcpy(state, data, 16);
|
||||
aes_addroundkey(state, &expanded_key[0]);
|
||||
for(int round = 1; round < 10; round++) {
|
||||
aes_subbytes(state);
|
||||
aes_shiftrows(state);
|
||||
aes_mixcolumns(state);
|
||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
||||
}
|
||||
aes_subbytes(state);
|
||||
aes_shiftrows(state);
|
||||
aes_addroundkey(state, &expanded_key[160]);
|
||||
memcpy(data, state, 16);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) {
|
||||
for(int i = 0; i < 16; i++) {
|
||||
round_keys[i] = key[i];
|
||||
}
|
||||
|
||||
for(int i = 4; i < 44; i++) {
|
||||
int prev_word_idx = (i - 1) * 4;
|
||||
uint8_t b0 = round_keys[prev_word_idx];
|
||||
uint8_t b1 = round_keys[prev_word_idx + 1];
|
||||
uint8_t b2 = round_keys[prev_word_idx + 2];
|
||||
uint8_t b3 = round_keys[prev_word_idx + 3];
|
||||
|
||||
if((i % 4) == 0) {
|
||||
uint8_t new_b0 = aes_sbox[b1] ^ aes_rcon[(i / 4) - 1];
|
||||
uint8_t new_b1 = aes_sbox[b2];
|
||||
uint8_t new_b2 = aes_sbox[b3];
|
||||
uint8_t new_b3 = aes_sbox[b0];
|
||||
b0 = new_b0;
|
||||
b1 = new_b1;
|
||||
b2 = new_b2;
|
||||
b3 = new_b3;
|
||||
}
|
||||
|
||||
int back_word_idx = (i - 4) * 4;
|
||||
b0 ^= round_keys[back_word_idx];
|
||||
b1 ^= round_keys[back_word_idx + 1];
|
||||
b2 ^= round_keys[back_word_idx + 2];
|
||||
b3 ^= round_keys[back_word_idx + 3];
|
||||
|
||||
int curr_word_idx = i * 4;
|
||||
round_keys[curr_word_idx] = b0;
|
||||
round_keys[curr_word_idx + 1] = b1;
|
||||
round_keys[curr_word_idx + 2] = b2;
|
||||
round_keys[curr_word_idx + 3] = b3;
|
||||
}
|
||||
}
|
||||
|
||||
static void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data) {
|
||||
uint8_t state[16];
|
||||
memcpy(state, data, 16);
|
||||
|
||||
aes_addroundkey(state, &expanded_key[160]);
|
||||
|
||||
for(int round = 9; round > 0; round--) {
|
||||
aes_shiftrows_inv(state);
|
||||
aes_subbytes_inv(state);
|
||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
||||
aes_mixcolumns_inv(state);
|
||||
}
|
||||
|
||||
aes_shiftrows_inv(state);
|
||||
aes_subbytes_inv(state);
|
||||
aes_addroundkey(state, &expanded_key[0]);
|
||||
|
||||
memcpy(data, state, 16);
|
||||
}
|
||||
|
||||
static void get_kia_v6_aes_key(uint8_t* aes_key) {
|
||||
uint64_t keystore_a = get_kia_v6_keystore_a();
|
||||
uint32_t keystore_a_hi = (keystore_a >> 32) & 0xFFFFFFFF;
|
||||
uint32_t keystore_a_lo = keystore_a & 0xFFFFFFFF;
|
||||
|
||||
uint32_t uVar15_a = keystore_a_lo ^ KIA_V6_XOR_MASK_LOW; // low part
|
||||
uint32_t uVar5_a = KIA_V6_XOR_MASK_HIGH ^ keystore_a_hi; // high part
|
||||
|
||||
uint64_t val64_a = ((uint64_t)uVar5_a << 32) | uVar15_a;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
aes_key[i] = (val64_a >> (56 - i * 8)) & 0xFF;
|
||||
}
|
||||
|
||||
uint64_t keystore_b = get_kia_v6_keystore_b();
|
||||
uint32_t keystore_b_hi = (keystore_b >> 32) & 0xFFFFFFFF;
|
||||
uint32_t keystore_b_lo = keystore_b & 0xFFFFFFFF;
|
||||
|
||||
uint32_t uVar15_b = keystore_b_lo ^ KIA_V6_XOR_MASK_LOW;
|
||||
uint32_t uVar5_b = KIA_V6_XOR_MASK_HIGH ^ keystore_b_hi;
|
||||
|
||||
uint64_t val64_b = ((uint64_t)uVar5_b << 32) | uVar15_b;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
aes_key[i + 8] = (val64_b >> (56 - i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void kia_v6_encrypt_payload(
|
||||
uint8_t fx_field,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint32_t cnt,
|
||||
uint32_t* out_part1_low,
|
||||
uint32_t* out_part1_high,
|
||||
uint32_t* out_part2_low,
|
||||
uint32_t* out_part2_high,
|
||||
uint16_t* out_part3) {
|
||||
uint8_t plain[16];
|
||||
memset(plain, 0, 16);
|
||||
plain[0] = fx_field;
|
||||
plain[4] = (serial >> 16) & 0xFF;
|
||||
plain[5] = (serial >> 8) & 0xFF;
|
||||
plain[6] = serial & 0xFF;
|
||||
plain[7] = btn & 0x0F;
|
||||
plain[8] = (cnt >> 24) & 0xFF;
|
||||
plain[9] = (cnt >> 16) & 0xFF;
|
||||
plain[10] = (cnt >> 8) & 0xFF;
|
||||
plain[11] = cnt & 0xFF;
|
||||
plain[12] = aes_sbox[cnt & 0xFF];
|
||||
plain[15] = kia_v6_crc8(plain, 15);
|
||||
|
||||
uint8_t aes_key[16];
|
||||
get_kia_v6_aes_key(aes_key);
|
||||
uint8_t expanded_key[176];
|
||||
aes_key_expansion(aes_key, expanded_key);
|
||||
aes128_encrypt(expanded_key, plain);
|
||||
|
||||
uint8_t fx_hi = 0x20 | (fx_field >> 4);
|
||||
uint8_t fx_lo = fx_field & 0x0F;
|
||||
*out_part1_high = ((uint32_t)fx_hi << 24) | ((uint32_t)fx_lo << 16) |
|
||||
((uint32_t)plain[0] << 8) | plain[1];
|
||||
*out_part1_low = ((uint32_t)plain[2] << 24) | ((uint32_t)plain[3] << 16) |
|
||||
((uint32_t)plain[4] << 8) | plain[5];
|
||||
*out_part2_high = ((uint32_t)plain[6] << 24) | ((uint32_t)plain[7] << 16) |
|
||||
((uint32_t)plain[8] << 8) | plain[9];
|
||||
*out_part2_low = ((uint32_t)plain[10] << 24) | ((uint32_t)plain[11] << 16) |
|
||||
((uint32_t)plain[12] << 8) | plain[13];
|
||||
*out_part3 = ((uint16_t)plain[14] << 8) | plain[15];
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool kia_v6_decrypt(SubGhzProtocolDecoderKiaV6* instance) {
|
||||
uint8_t encrypted_data[16];
|
||||
|
||||
encrypted_data[0] = (instance->stored_part1_high >> 8) & 0xFF;
|
||||
encrypted_data[1] = instance->stored_part1_high & 0xFF;
|
||||
|
||||
encrypted_data[2] = (instance->stored_part1_low >> 24) & 0xFF;
|
||||
encrypted_data[3] = (instance->stored_part1_low >> 16) & 0xFF;
|
||||
encrypted_data[4] = (instance->stored_part1_low >> 8) & 0xFF;
|
||||
encrypted_data[5] = instance->stored_part1_low & 0xFF;
|
||||
|
||||
encrypted_data[6] = (instance->stored_part2_high >> 24) & 0xFF;
|
||||
encrypted_data[7] = (instance->stored_part2_high >> 16) & 0xFF;
|
||||
encrypted_data[8] = (instance->stored_part2_high >> 8) & 0xFF;
|
||||
encrypted_data[9] = instance->stored_part2_high & 0xFF;
|
||||
|
||||
encrypted_data[10] = (instance->stored_part2_low >> 24) & 0xFF;
|
||||
encrypted_data[11] = (instance->stored_part2_low >> 16) & 0xFF;
|
||||
encrypted_data[12] = (instance->stored_part2_low >> 8) & 0xFF;
|
||||
encrypted_data[13] = instance->stored_part2_low & 0xFF;
|
||||
|
||||
encrypted_data[14] = (instance->data_part3 >> 8) & 0xFF;
|
||||
encrypted_data[15] = instance->data_part3 & 0xFF;
|
||||
|
||||
uint8_t fx_byte0 = (instance->stored_part1_high >> 24) & 0xFF;
|
||||
uint8_t fx_byte1 = (instance->stored_part1_high >> 16) & 0xFF;
|
||||
instance->fx_field = ((fx_byte0 & 0xF) << 4) | (fx_byte1 & 0xF);
|
||||
|
||||
uint8_t aes_key[16];
|
||||
get_kia_v6_aes_key(aes_key);
|
||||
uint8_t expanded_key[176];
|
||||
aes_key_expansion(aes_key, expanded_key);
|
||||
aes128_decrypt(expanded_key, encrypted_data);
|
||||
|
||||
uint8_t* decrypted = encrypted_data;
|
||||
|
||||
uint8_t calculated_crc = kia_v6_crc8(decrypted, 15);
|
||||
uint8_t stored_crc = decrypted[15];
|
||||
|
||||
// Serial: bytes 4-6 as 24-bit big-endian
|
||||
instance->generic.serial = ((uint32_t)decrypted[4] << 16) | ((uint32_t)decrypted[5] << 8) |
|
||||
decrypted[6];
|
||||
|
||||
instance->generic.btn = decrypted[7];
|
||||
|
||||
instance->generic.cnt = ((uint32_t)decrypted[8] << 24) | ((uint32_t)decrypted[9] << 16) |
|
||||
((uint32_t)decrypted[10] << 8) | decrypted[11];
|
||||
|
||||
instance->crc1_field = decrypted[12];
|
||||
|
||||
instance->crc2_field = decrypted[15];
|
||||
|
||||
return (calculated_crc ^ stored_crc) < 2;
|
||||
}
|
||||
|
||||
void* kia_protocol_decoder_v6_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolDecoderKiaV6* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV6));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(SubGhzProtocolDecoderKiaV6));
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v6;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v6_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v6_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
uint32_t uVar4, uVar5;
|
||||
ManchesterEvent event;
|
||||
bool data_bit;
|
||||
uint8_t bit_count_inc;
|
||||
uint32_t step_value;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV6DecoderStepReset: // case 0
|
||||
if(level == 0) {
|
||||
return;
|
||||
}
|
||||
if(DURATION_DIFF(duration, kia_protocol_v6_const.te_short) <
|
||||
kia_protocol_v6_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepWaitFirstHigh;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
return;
|
||||
|
||||
case KiaV6DecoderStepWaitFirstHigh: // case 1
|
||||
if(level != 0) {
|
||||
return;
|
||||
}
|
||||
uint32_t diff_short = DURATION_DIFF(duration, kia_protocol_v6_const.te_short);
|
||||
uint32_t diff_long = DURATION_DIFF(duration, kia_protocol_v6_const.te_long);
|
||||
|
||||
uint32_t diff = (diff_long < diff_short) ? diff_long : diff_short;
|
||||
|
||||
if(diff_long < kia_protocol_v6_const.te_delta && diff_long < diff_short) {
|
||||
if(instance->header_count >= 0x259) { // 601 decimal
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepWaitLongHigh;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(diff >= kia_protocol_v6_const.te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v6_const.te_short) <
|
||||
kia_protocol_v6_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
return;
|
||||
} else {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
|
||||
case KiaV6DecoderStepWaitLongHigh: // case 2
|
||||
if(level == 0) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
uint32_t diff_long_check = DURATION_DIFF(duration, kia_protocol_v6_const.te_long);
|
||||
uint32_t diff_short_check = DURATION_DIFF(duration, kia_protocol_v6_const.te_short);
|
||||
|
||||
if(diff_long_check >= kia_protocol_v6_const.te_delta) {
|
||||
if(diff_short_check >= kia_protocol_v6_const.te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
}
|
||||
|
||||
if(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v6_const.te_long) >=
|
||||
kia_protocol_v6_const.te_delta) {
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
|
||||
instance->data_part1_low = (uint32_t)(instance->decoder.decode_data & 0xFFFFFFFF);
|
||||
instance->data_part1_high = (uint32_t)((instance->decoder.decode_data >> 32) & 0xFFFFFFFF);
|
||||
instance->bit_count = instance->decoder.decode_count_bit;
|
||||
|
||||
instance->decoder.parser_step = KiaV6DecoderStepData;
|
||||
return;
|
||||
|
||||
case KiaV6DecoderStepData: // case 3
|
||||
if(DURATION_DIFF(duration, kia_protocol_v6_const.te_short) <
|
||||
kia_protocol_v6_const.te_delta) {
|
||||
event = (level & 0x7F) << 1;
|
||||
goto manchester_process;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v6_const.te_long) <
|
||||
kia_protocol_v6_const.te_delta) {
|
||||
event = level ? 6 : 4;
|
||||
goto manchester_process;
|
||||
}
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
|
||||
manchester_process:
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
uVar4 = instance->data_part1_low;
|
||||
uVar5 = (uVar4 << 1) | (data_bit ? 1 : 0);
|
||||
|
||||
uint32_t carry = (uVar4 >> 31) & 1;
|
||||
uVar4 = (instance->data_part1_high << 1) | carry;
|
||||
|
||||
instance->data_part1_low = uVar5;
|
||||
instance->data_part1_high = uVar4;
|
||||
|
||||
instance->decoder.decode_data = ((uint64_t)uVar4 << 32) | uVar5;
|
||||
|
||||
bit_count_inc = instance->bit_count + 1;
|
||||
instance->bit_count = bit_count_inc;
|
||||
|
||||
if(bit_count_inc == 0x40) {
|
||||
instance->stored_part1_low = ~uVar5;
|
||||
instance->stored_part1_high = ~uVar4;
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
} else if(bit_count_inc == 0x80) {
|
||||
instance->stored_part2_low = ~uVar5;
|
||||
instance->stored_part2_high = ~uVar4;
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
|
||||
if(instance->bit_count != kia_protocol_v6_const.min_count_bit_for_found) {
|
||||
return;
|
||||
}
|
||||
instance->generic.data_count_bit = kia_protocol_v6_const.min_count_bit_for_found;
|
||||
instance->data_part3 = ~((uint16_t)instance->data_part1_low);
|
||||
|
||||
kia_v6_decrypt(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
instance->bit_count = 0;
|
||||
step_value = KiaV6DecoderStepReset;
|
||||
goto LAB_reset;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
LAB_reset:
|
||||
instance->decoder.parser_step = step_value;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t kia_protocol_decoder_v6_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
uint8_t hash = 0;
|
||||
uint8_t* data = (uint8_t*)&instance->data_part1_low;
|
||||
size_t data_size = (instance->bit_count + 7) / 8;
|
||||
size_t max_size = (kia_protocol_v6_const.min_count_bit_for_found + 7) / 8;
|
||||
if(data_size > max_size) {
|
||||
data_size = max_size;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < data_size; i++) {
|
||||
hash ^= data[i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v6_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_FREQUENCY, &preset->frequency, 1))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, FF_PRESET, furi_string_get_cstr(preset->name)))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, FF_PROTOCOL, instance->generic.protocol_name))
|
||||
break;
|
||||
|
||||
uint32_t bits = kia_protocol_v6_const.min_count_bit_for_found;
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_BIT, &bits, 1)) break;
|
||||
|
||||
uint64_t key_data = ((uint64_t)instance->stored_part1_high << 32) |
|
||||
instance->stored_part1_low;
|
||||
char key_str[20];
|
||||
snprintf(key_str, sizeof(key_str), "%016llX", key_data);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, FF_KEY, key_str)) break;
|
||||
|
||||
if(pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0) != SubGhzProtocolStatusOk)
|
||||
break;
|
||||
|
||||
uint32_t key2_low = instance->stored_part2_low;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_2", &key2_low, 1)) break;
|
||||
|
||||
uint32_t key2_high = instance->stored_part2_high;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_3", &key2_high, 1)) break;
|
||||
|
||||
uint32_t key3 = instance->data_part3;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_4", &key3, 1)) break;
|
||||
|
||||
uint32_t fx = instance->fx_field;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Fx", &fx, 1)) break;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v6_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
if(!subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, kia_protocol_v6_const.min_count_bit_for_found)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t key = instance->generic.data;
|
||||
|
||||
instance->stored_part1_low = (uint32_t)(key & 0xFFFFFFFF);
|
||||
instance->stored_part1_high = (uint32_t)((key >> 32) & 0xFFFFFFFF);
|
||||
|
||||
uint32_t temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_2", &temp, 1)) {
|
||||
instance->stored_part2_low = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_3", &temp, 1)) {
|
||||
instance->stored_part2_high = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_4", &temp, 1)) {
|
||||
instance->data_part3 = (uint16_t)temp;
|
||||
}
|
||||
|
||||
uint32_t serial = instance->generic.serial;
|
||||
uint32_t btn = instance->generic.btn;
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
instance->generic.cnt = (uint16_t)cnt;
|
||||
if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) {
|
||||
instance->fx_field = (uint8_t)temp;
|
||||
}
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v6_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
kia_v6_decrypt(instance);
|
||||
|
||||
uint32_t key1_hi = instance->stored_part1_high;
|
||||
uint32_t key1_lo = instance->stored_part1_low;
|
||||
uint32_t key2_hi = instance->stored_part2_high;
|
||||
uint32_t key2_lo = instance->stored_part2_low;
|
||||
|
||||
uint32_t key2_uVar4 = key2_hi << 16;
|
||||
uint32_t key2_uVar2 = key2_lo >> 16;
|
||||
uint32_t key2_uVar1 = key2_hi >> 16;
|
||||
uint32_t key2_combined = key2_uVar4 | key2_uVar2;
|
||||
|
||||
uint32_t key2_uVar3 = key2_lo << 16;
|
||||
uint32_t key2_second = (instance->data_part3 & 0xFFFF) | key2_uVar3;
|
||||
|
||||
uint32_t serial_6 = instance->generic.serial & 0xFFFFFF;
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"%08lX%08lX%04lX\r\n"
|
||||
"%08lX%08lX Fx:%02X\r\n"
|
||||
"Ser:%06lX Btn:%01X CRC1:%02X\r\n"
|
||||
"Cnt:%08lX CRC2:%02X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
key1_hi,
|
||||
key1_lo,
|
||||
key2_uVar1,
|
||||
key2_combined,
|
||||
key2_second,
|
||||
instance->fx_field,
|
||||
serial_6,
|
||||
instance->generic.btn & 0x0F,
|
||||
instance->crc1_field,
|
||||
instance->generic.cnt,
|
||||
instance->crc2_field);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
#define KIA_V6_PREAMBLE_PAIRS_1 640
|
||||
#define KIA_V6_PREAMBLE_PAIRS_2 38
|
||||
#define KIA_V6_TE_SHORT 200
|
||||
#define KIA_V6_TE_LONG 400
|
||||
#define KIA_V6_UPLOAD_SIZE 1928
|
||||
_Static_assert(
|
||||
KIA_V6_UPLOAD_SIZE <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V6_UPLOAD_SIZE exceeds shared upload slab");
|
||||
|
||||
static inline void
|
||||
kia_v6_encode_manchester_bit(LevelDuration* upload, size_t* index, bool bit, uint32_t te) {
|
||||
if(bit) {
|
||||
upload[(*index)++] = level_duration_make(false, te);
|
||||
upload[(*index)++] = level_duration_make(true, te);
|
||||
} else {
|
||||
upload[(*index)++] = level_duration_make(true, te);
|
||||
upload[(*index)++] = level_duration_make(false, te);
|
||||
}
|
||||
}
|
||||
|
||||
static void kia_v6_encode_message(
|
||||
LevelDuration* upload,
|
||||
size_t* index,
|
||||
int preamble_pairs,
|
||||
uint32_t p1_lo,
|
||||
uint32_t p1_hi,
|
||||
uint32_t p2_lo,
|
||||
uint32_t p2_hi,
|
||||
uint16_t p3) {
|
||||
const uint32_t te_short = KIA_V6_TE_SHORT;
|
||||
const uint32_t te_long = KIA_V6_TE_LONG;
|
||||
|
||||
for(int i = 0; i < preamble_pairs; i++) {
|
||||
upload[(*index)++] = level_duration_make(true, te_short);
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
}
|
||||
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
upload[(*index)++] = level_duration_make(true, te_long);
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
|
||||
for(int b = 60; b >= 0; b--) {
|
||||
uint32_t word = (b >= 32) ? p1_hi : p1_lo;
|
||||
int shift = (b >= 32) ? (b - 32) : b;
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
|
||||
}
|
||||
|
||||
for(int b = 63; b >= 0; b--) {
|
||||
uint32_t word = (b >= 32) ? p2_hi : p2_lo;
|
||||
int shift = (b >= 32) ? (b - 32) : b;
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
|
||||
}
|
||||
|
||||
for(int b = 15; b >= 0; b--) {
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~p3) >> b) & 1, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
static void kia_protocol_encoder_v6_build_upload(SubGhzProtocolEncoderKiaV6* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
kia_v6_encrypt_payload(
|
||||
instance->fx_field,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn & 0x0F,
|
||||
instance->generic.cnt,
|
||||
&instance->stored_part1_low,
|
||||
&instance->stored_part1_high,
|
||||
&instance->stored_part2_low,
|
||||
&instance->stored_part2_high,
|
||||
&instance->data_part3);
|
||||
|
||||
uint32_t p1_lo = instance->stored_part1_low;
|
||||
uint32_t p1_hi = instance->stored_part1_high;
|
||||
uint32_t p2_lo = instance->stored_part2_low;
|
||||
uint32_t p2_hi = instance->stored_part2_high;
|
||||
uint16_t p3 = instance->data_part3;
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
kia_v6_encode_message(
|
||||
instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_1, p1_lo, p1_hi, p2_lo, p2_hi, p3);
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, KIA_V6_TE_LONG);
|
||||
|
||||
kia_v6_encode_message(
|
||||
instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_2, p1_lo, p1_hi, p2_lo, p2_hi, p3);
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, KIA_V6_TE_LONG);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
#ifndef REMOVE_LOGS
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encrypted payload (TX): P1=%08lX%08lX P2=%08lX%08lX P3=%04X",
|
||||
(unsigned long)p1_hi,
|
||||
(unsigned long)p1_lo,
|
||||
(unsigned long)p2_hi,
|
||||
(unsigned long)p2_lo,
|
||||
(unsigned int)p3);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder input: Ser=0x%06lX Btn=%u Cnt=0x%08lX -> upload %u entries, repeat %ld",
|
||||
(unsigned long)instance->generic.serial,
|
||||
(unsigned int)(instance->generic.btn & 0x0F),
|
||||
(unsigned long)instance->generic.cnt,
|
||||
(unsigned)index,
|
||||
(long)instance->encoder.repeat);
|
||||
#endif
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
|
||||
if(!instance) return NULL;
|
||||
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
|
||||
|
||||
if(environment) {
|
||||
protopirate_keys_load(environment);
|
||||
}
|
||||
|
||||
instance->base.protocol = &kia_protocol_v6;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
pp_encoder_buffer_ensure(instance, KIA_V6_UPLOAD_SIZE);
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV6* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->generic.data_count_bit = kia_protocol_v6_const.min_count_bit_for_found;
|
||||
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
uint32_t serial = 0;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
if(btn == UINT32_MAX || cnt == UINT32_MAX) {
|
||||
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)(btn & 0x0F);
|
||||
instance->generic.cnt = cnt;
|
||||
|
||||
uint32_t fx_temp = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Fx", &fx_temp, 1)) {
|
||||
instance->fx_field = (uint8_t)(fx_temp & 0xFF);
|
||||
} else {
|
||||
instance->fx_field = 0;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = (int32_t)(pp_encoder_read_repeat(flipper_format, 10) & 0x7FFFFFFF);
|
||||
|
||||
kia_protocol_encoder_v6_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
#endif // ENABLE_EMULATE_FEATURE
|
||||
@@ -0,0 +1,44 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include "kia_generic.h"
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V6_NAME "Kia V6"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV6 SubGhzProtocolDecoderKiaV6;
|
||||
typedef struct SubGhzProtocolEncoderKiaV6 SubGhzProtocolEncoderKiaV6;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v6_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v6_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v6;
|
||||
|
||||
// Decoder functions
|
||||
void* kia_protocol_decoder_v6_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v6_free(void* context);
|
||||
void kia_protocol_decoder_v6_reset(void* context);
|
||||
void kia_protocol_decoder_v6_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t kia_protocol_decoder_v6_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v6_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v6_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* kia_protocol_encoder_v6_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v6_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
#endif
|
||||
@@ -0,0 +1,610 @@
|
||||
#include "kia_v7.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.h>
|
||||
|
||||
#define KIA_V7_UPLOAD_CAPACITY \
|
||||
(1U + (KIA_V7_PREAMBLE_PAIRS * 2U) + 1U + (KIA_V7_KEY_BITS * 2U) + 2U)
|
||||
#define KIA_V7_PREAMBLE_PAIRS 0x13F
|
||||
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
|
||||
#define KIA_V7_HEADER 0x4C
|
||||
#define KIA_V7_TAIL_GAP_US 0x7D0
|
||||
#define KIA_V7_KEY_BITS 64U
|
||||
#define KIA_V7_DEFAULT_TX_REPEAT 10U
|
||||
_Static_assert(
|
||||
KIA_V7_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"KIA_V7_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v7_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = KIA_V7_KEY_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV7DecoderStepReset = 0,
|
||||
KiaV7DecoderStepPreamble = 1,
|
||||
KiaV7DecoderStepSyncLow = 2,
|
||||
KiaV7DecoderStepData = 3,
|
||||
} KiaV7DecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV7 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t decoded_button;
|
||||
uint8_t fixed_high_byte;
|
||||
uint8_t crc_calculated;
|
||||
uint8_t crc_raw;
|
||||
bool crc_valid;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
struct SubGhzProtocolEncoderKiaV7 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t tx_bit_count;
|
||||
uint8_t decoded_button;
|
||||
uint8_t fixed_high_byte;
|
||||
uint8_t crc_calculated;
|
||||
uint8_t crc_raw;
|
||||
bool crc_valid;
|
||||
};
|
||||
#endif
|
||||
|
||||
#define kia_v7_crc8(data, len) subghz_protocol_blocks_crc8((data), (len), 0x7F, 0x4C)
|
||||
|
||||
static const char* kia_v7_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
case 0x03:
|
||||
case 0x08:
|
||||
return "Trunk";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
kia_v7_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) {
|
||||
return pp_write_display(flipper_format, protocol_name, kia_v7_get_button_name(button));
|
||||
}
|
||||
|
||||
static void kia_v7_decode_key_common(
|
||||
SubGhzBlockGeneric* generic,
|
||||
uint8_t* decoded_button,
|
||||
uint8_t* fixed_high_byte,
|
||||
uint8_t* crc_calculated,
|
||||
uint8_t* crc_raw,
|
||||
bool* crc_valid) {
|
||||
uint8_t bytes[8];
|
||||
pp_u64_to_bytes_be(generic->data, bytes);
|
||||
|
||||
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
|
||||
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
|
||||
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
|
||||
const uint8_t button = bytes[6] & 0x0FU;
|
||||
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
|
||||
const uint8_t crc_pkt = bytes[7];
|
||||
|
||||
generic->serial = serial & 0x0FFFFFFFU;
|
||||
generic->btn = button;
|
||||
generic->cnt = counter;
|
||||
generic->data_count_bit = KIA_V7_KEY_BITS;
|
||||
|
||||
if(decoded_button) {
|
||||
*decoded_button = button;
|
||||
}
|
||||
if(fixed_high_byte) {
|
||||
*fixed_high_byte = bytes[0];
|
||||
}
|
||||
if(crc_calculated) {
|
||||
*crc_calculated = crc_calc;
|
||||
}
|
||||
if(crc_raw) {
|
||||
*crc_raw = crc_pkt;
|
||||
}
|
||||
if(crc_valid) {
|
||||
*crc_valid = (crc_calc == crc_pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
|
||||
kia_v7_decode_key_common(
|
||||
&instance->generic,
|
||||
&instance->decoded_button,
|
||||
&instance->fixed_high_byte,
|
||||
&instance->crc_calculated,
|
||||
&instance->crc_raw,
|
||||
&instance->crc_valid);
|
||||
}
|
||||
|
||||
static uint64_t kia_v7_encode_key(
|
||||
uint8_t fixed_high_byte,
|
||||
uint32_t serial,
|
||||
uint8_t button,
|
||||
uint16_t counter,
|
||||
uint8_t* crc_out) {
|
||||
uint8_t bytes[8];
|
||||
|
||||
serial &= 0x0FFFFFFFU;
|
||||
button &= 0x0FU;
|
||||
|
||||
bytes[0] = fixed_high_byte;
|
||||
bytes[1] = (counter >> 8U) & 0xFFU;
|
||||
bytes[2] = counter & 0xFFU;
|
||||
bytes[3] = (serial >> 20U) & 0xFFU;
|
||||
bytes[4] = (serial >> 12U) & 0xFFU;
|
||||
bytes[5] = (serial >> 4U) & 0xFFU;
|
||||
bytes[6] = ((serial & 0x0FU) << 4U) | button;
|
||||
bytes[7] = kia_v7_crc8(bytes, 7);
|
||||
|
||||
if(crc_out) {
|
||||
*crc_out = bytes[7];
|
||||
}
|
||||
|
||||
return pp_bytes_to_u64_be(bytes);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
kia_v7_decode_key_common(
|
||||
&instance->generic,
|
||||
&instance->decoded_button,
|
||||
&instance->fixed_high_byte,
|
||||
&instance->crc_calculated,
|
||||
&instance->crc_raw,
|
||||
&instance->crc_valid);
|
||||
}
|
||||
|
||||
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
const LevelDuration high_short = level_duration_make(true, kia_protocol_v7_const.te_short);
|
||||
const LevelDuration low_short = level_duration_make(false, kia_protocol_v7_const.te_short);
|
||||
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
|
||||
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
|
||||
|
||||
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
|
||||
instance->tx_bit_count :
|
||||
64U;
|
||||
|
||||
size_t final_size = 0;
|
||||
|
||||
for(uint8_t pass = 0; pass < 2; pass++) {
|
||||
size_t index = pass;
|
||||
|
||||
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
instance->encoder.upload[index++] = low_short;
|
||||
}
|
||||
|
||||
if((index + 1U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
|
||||
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
|
||||
instance->encoder.upload[index++] = value ? high_short : low_short;
|
||||
instance->encoder.upload[index++] = value ? low_short : high_short;
|
||||
}
|
||||
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
instance->encoder.upload[index++] = low_tail;
|
||||
|
||||
final_size = index;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = final_size;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
|
||||
.alloc = kia_protocol_decoder_v7_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = kia_protocol_decoder_v7_feed,
|
||||
.reset = kia_protocol_decoder_v7_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = kia_protocol_decoder_v7_serialize,
|
||||
.deserialize = kia_protocol_decoder_v7_deserialize,
|
||||
.get_string = kia_protocol_decoder_v7_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
|
||||
.alloc = kia_protocol_encoder_v7_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = kia_protocol_encoder_v7_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol kia_protocol_v7 = {
|
||||
.name = KIA_PROTOCOL_V7_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &kia_protocol_v7_decoder,
|
||||
.encoder = &kia_protocol_v7_encoder,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &kia_protocol_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 1;
|
||||
pp_encoder_buffer_ensure(instance, KIA_V7_UPLOAD_CAPACITY);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
|
||||
|
||||
do {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||
if(load_st != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->tx_bit_count =
|
||||
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
|
||||
(uint8_t)instance->generic.data_count_bit :
|
||||
64U;
|
||||
|
||||
kia_v7_decode_key_encoder(instance);
|
||||
|
||||
uint32_t serial = instance->generic.serial;
|
||||
uint32_t btn = instance->generic.btn;
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
|
||||
instance->generic.serial = serial & 0x0FFFFFFFU;
|
||||
instance->generic.btn = (uint8_t)btn & 0x0FU;
|
||||
instance->generic.cnt = cnt & 0xFFFFU;
|
||||
|
||||
instance->generic.data = kia_v7_encode_key(
|
||||
instance->fixed_high_byte,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)instance->generic.cnt,
|
||||
&instance->crc_calculated);
|
||||
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||
|
||||
instance->encoder.repeat =
|
||||
pp_encoder_read_repeat(flipper_format, KIA_V7_DEFAULT_TX_REPEAT);
|
||||
|
||||
if(!kia_v7_encoder_get_upload(instance)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
pp_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
if(!flipper_format_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &kia_protocol_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool data = false;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV7DecoderStepReset:
|
||||
if(level && pp_is_short(duration, &kia_protocol_v7_const)) {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->preamble_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepPreamble:
|
||||
if(level) {
|
||||
if(pp_is_long(duration, &kia_protocol_v7_const) &&
|
||||
pp_is_short(instance->decoder.te_last, &kia_protocol_v7_const)) {
|
||||
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
} else if(pp_is_short(duration, &kia_protocol_v7_const)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(pp_is_short(duration, &kia_protocol_v7_const) &&
|
||||
pp_is_short(instance->decoder.te_last, &kia_protocol_v7_const)) {
|
||||
instance->preamble_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepSyncLow:
|
||||
if(!level && pp_is_short(duration, &kia_protocol_v7_const) &&
|
||||
pp_is_long(instance->decoder.te_last, &kia_protocol_v7_const)) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepData;
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepData: {
|
||||
if(pp_is_short(duration, &kia_protocol_v7_const)) {
|
||||
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
|
||||
} else if(pp_is_long(duration, &kia_protocol_v7_const)) {
|
||||
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
} else {
|
||||
event = ManchesterEventReset;
|
||||
}
|
||||
|
||||
if(pp_is_short(duration, &kia_protocol_v7_const) ||
|
||||
pp_is_long(duration, &kia_protocol_v7_const)) {
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, data);
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit == KIA_V7_KEY_BITS) {
|
||||
const uint64_t candidate = ~instance->decoder.decode_data;
|
||||
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
|
||||
|
||||
if(hdr == KIA_V7_HEADER) {
|
||||
instance->generic.data = candidate;
|
||||
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
if(instance->crc_valid) {
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
} else {
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
} else {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Cnt:%04lX\r\n"
|
||||
"Btn:%01X [%s] CRC:%02X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.data,
|
||||
instance->generic.serial & 0x0FFFFFFFU,
|
||||
instance->generic.cnt & 0xFFFFU,
|
||||
instance->decoded_button & 0x0FU,
|
||||
kia_v7_get_button_name(instance->decoded_button),
|
||||
instance->crc_calculated,
|
||||
instance->crc_valid ? "OK" : "ERR");
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
status = pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial & 0x0FFFFFFFU,
|
||||
(uint32_t)(instance->decoded_button & 0x0FU),
|
||||
(uint32_t)(instance->generic.cnt & 0xFFFFU),
|
||||
0);
|
||||
if(status != SubGhzProtocolStatusOk) return status;
|
||||
|
||||
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
|
||||
if(!flipper_format_write_uint32(flipper_format, FF_REPEAT, &repeat_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return kia_v7_write_display(
|
||||
flipper_format, instance->generic.protocol_name, instance->decoded_button);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
uint32_t ser_u32 = 0;
|
||||
uint32_t btn_u32 = 0;
|
||||
uint32_t cnt_u32 = 0;
|
||||
bool got_serial = false;
|
||||
bool got_btn = false;
|
||||
bool got_cnt = false;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_serial = flipper_format_read_uint32(flipper_format, FF_SERIAL, &ser_u32, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_btn = flipper_format_read_uint32(flipper_format, FF_BTN, &btn_u32, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_cnt = flipper_format_read_uint32(flipper_format, FF_CNT, &cnt_u32, 1);
|
||||
|
||||
if(got_serial || got_btn || got_cnt) {
|
||||
if(got_serial) {
|
||||
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
|
||||
}
|
||||
if(got_btn) {
|
||||
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
|
||||
}
|
||||
if(got_cnt) {
|
||||
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
|
||||
}
|
||||
instance->generic.data = kia_v7_encode_key(
|
||||
instance->fixed_high_byte,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn & 0x0FU,
|
||||
(uint16_t)(instance->generic.cnt & 0xFFFFU),
|
||||
&instance->crc_calculated);
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#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/level_duration.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define KIA_PROTOCOL_V7_NAME "Kia V7"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
|
||||
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v7_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v7_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v7;
|
||||
|
||||
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v7_free(void* context);
|
||||
void kia_protocol_decoder_v7_reset(void* context);
|
||||
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
#endif
|
||||
@@ -0,0 +1,877 @@
|
||||
#include "land_rover_v0.h"
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "LandRoverV0"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_land_rover_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 81,
|
||||
};
|
||||
|
||||
#define LAND_ROVER_V0_PREAMBLE_PAIRS 319U
|
||||
#define LAND_ROVER_V0_MIN_PREAMBLE_PAIRS 64U
|
||||
#define LAND_ROVER_V0_SYNC_US 750U
|
||||
#define LAND_ROVER_V0_SYNC_DELTA_US 120U
|
||||
#define LAND_ROVER_V0_UPLOAD_CAPACITY 1024U
|
||||
#define LAND_ROVER_V0_GAP_US 50000U
|
||||
_Static_assert(
|
||||
LAND_ROVER_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"LAND_ROVER_V0_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
|
||||
#define LAND_ROVER_V0_BTN_UNKNOWN 0x00U
|
||||
#define LAND_ROVER_V0_BTN_LOCK 0x02U
|
||||
#define LAND_ROVER_V0_BTN_UNLOCK 0x04U
|
||||
|
||||
#define LAND_ROVER_V0_SIG_UNLOCK 0xA285E3UL
|
||||
#define LAND_ROVER_V0_SIG_LOCK 0xC20363UL
|
||||
|
||||
#define LAND_ROVER_V0_FF_BTNSIG "BtnSig"
|
||||
#define LAND_ROVER_V0_FF_CHECK "Check"
|
||||
#define LAND_ROVER_V0_FF_TAIL "Tail"
|
||||
#define LAND_ROVER_V0_FF_EXTRA_BIT "ExtraBit"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderLandRoverV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw[10];
|
||||
uint8_t bit_count;
|
||||
bool extra_bit;
|
||||
bool previous_bit;
|
||||
bool boundary_pad_skipped;
|
||||
bool pending_short;
|
||||
|
||||
uint64_t key;
|
||||
uint16_t tail;
|
||||
uint32_t command_signature;
|
||||
uint32_t serial;
|
||||
uint32_t count;
|
||||
uint8_t button;
|
||||
uint8_t check;
|
||||
bool check_ok;
|
||||
bool tail_ok;
|
||||
} SubGhzProtocolDecoderLandRoverV0;
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderLandRoverV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t key;
|
||||
uint16_t tail;
|
||||
uint32_t command_signature;
|
||||
uint32_t serial;
|
||||
uint32_t count;
|
||||
uint8_t button;
|
||||
uint8_t check;
|
||||
} SubGhzProtocolEncoderLandRoverV0;
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LandRoverV0DecoderStepReset = 0,
|
||||
LandRoverV0DecoderStepPreambleLow,
|
||||
LandRoverV0DecoderStepPreambleHigh,
|
||||
LandRoverV0DecoderStepSyncLow,
|
||||
LandRoverV0DecoderStepData,
|
||||
} LandRoverV0DecoderStep;
|
||||
|
||||
static uint8_t land_rover_v0_button_from_signature(uint32_t signature);
|
||||
static const char* land_rover_v0_button_name(uint8_t button);
|
||||
static uint8_t land_rover_v0_calculate_check(uint32_t count);
|
||||
static bool land_rover_v0_calculate_tail_msb(uint32_t count);
|
||||
static uint16_t land_rover_v0_calculate_tail(uint32_t count);
|
||||
static void land_rover_v0_parse_key_fields(
|
||||
uint64_t key,
|
||||
uint32_t* signature,
|
||||
uint32_t* serial,
|
||||
uint32_t* count,
|
||||
uint8_t* button,
|
||||
uint8_t* check);
|
||||
static bool land_rover_v0_validate_frame(
|
||||
uint64_t key,
|
||||
uint16_t tail,
|
||||
bool extra_bit,
|
||||
bool* check_ok,
|
||||
bool* tail_ok);
|
||||
static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* instance, bool bit);
|
||||
static bool land_rover_v0_process_transition(
|
||||
SubGhzProtocolDecoderLandRoverV0* instance,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static bool land_rover_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderLandRoverV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool land_rover_v0_encoder_add_bit(
|
||||
SubGhzProtocolEncoderLandRoverV0* instance,
|
||||
size_t* index,
|
||||
bool* previous_bit,
|
||||
bool bit);
|
||||
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance);
|
||||
#endif
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_land_rover_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_land_rover_v0_alloc,
|
||||
.free = subghz_protocol_decoder_land_rover_v0_free,
|
||||
.feed = subghz_protocol_decoder_land_rover_v0_feed,
|
||||
.reset = subghz_protocol_decoder_land_rover_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_land_rover_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_land_rover_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_land_rover_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_land_rover_v0_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_land_rover_v0_alloc,
|
||||
.free = subghz_protocol_encoder_land_rover_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_land_rover_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_land_rover_v0_stop,
|
||||
.yield = subghz_protocol_encoder_land_rover_v0_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_land_rover_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol land_rover_v0_protocol = {
|
||||
.name = LAND_ROVER_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_land_rover_v0_decoder,
|
||||
.encoder = &subghz_protocol_land_rover_v0_encoder,
|
||||
};
|
||||
|
||||
static bool land_rover_v0_is_short(uint32_t duration) {
|
||||
return pp_is_short(duration, &subghz_protocol_land_rover_v0_const);
|
||||
}
|
||||
|
||||
static bool land_rover_v0_is_long(uint32_t duration) {
|
||||
return pp_is_long(duration, &subghz_protocol_land_rover_v0_const);
|
||||
}
|
||||
|
||||
static bool land_rover_v0_is_sync(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, LAND_ROVER_V0_SYNC_US) < LAND_ROVER_V0_SYNC_DELTA_US;
|
||||
}
|
||||
|
||||
static uint8_t land_rover_v0_button_from_signature(uint32_t signature) {
|
||||
if(signature == LAND_ROVER_V0_SIG_UNLOCK) {
|
||||
return LAND_ROVER_V0_BTN_UNLOCK;
|
||||
} else if(signature == LAND_ROVER_V0_SIG_LOCK) {
|
||||
return LAND_ROVER_V0_BTN_LOCK;
|
||||
}
|
||||
return LAND_ROVER_V0_BTN_UNKNOWN;
|
||||
}
|
||||
|
||||
static const char* land_rover_v0_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case LAND_ROVER_V0_BTN_LOCK:
|
||||
return "Lock";
|
||||
case LAND_ROVER_V0_BTN_UNLOCK:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t land_rover_v0_calculate_check(uint32_t count) {
|
||||
const uint8_t c0 = ((count >> 1) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 6)) &
|
||||
1U;
|
||||
const uint8_t c1 = ((count >> 0) ^ (count >> 2) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^
|
||||
(count >> 6) ^ 1U) &
|
||||
1U;
|
||||
const uint8_t c2 = ((count >> 1) ^ (count >> 3) ^ (count >> 4) ^ (count >> 5) ^ (count >> 6)) &
|
||||
1U;
|
||||
|
||||
return (uint8_t)(c0 | (c1 << 1) | (c2 << 2));
|
||||
}
|
||||
|
||||
static bool land_rover_v0_calculate_tail_msb(uint32_t count) {
|
||||
const uint8_t tail = ((count >> 0) ^ (count >> 2) ^ (count >> 4) ^ (count >> 5)) & 1U;
|
||||
return tail != 0U;
|
||||
}
|
||||
|
||||
static uint16_t land_rover_v0_calculate_tail(uint32_t count) {
|
||||
return land_rover_v0_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
|
||||
}
|
||||
|
||||
static void land_rover_v0_parse_key_fields(
|
||||
uint64_t key,
|
||||
uint32_t* signature,
|
||||
uint32_t* serial,
|
||||
uint32_t* count,
|
||||
uint8_t* button,
|
||||
uint8_t* check) {
|
||||
uint8_t key_bytes[8];
|
||||
pp_u64_to_bytes_be(key, key_bytes);
|
||||
|
||||
const uint32_t sig = ((uint32_t)key_bytes[0] << 16) | ((uint32_t)key_bytes[1] << 8) |
|
||||
key_bytes[2];
|
||||
const uint32_t sn = ((uint32_t)key_bytes[3] << 16) | ((uint32_t)key_bytes[4] << 8) |
|
||||
key_bytes[5];
|
||||
const uint32_t cnt = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
|
||||
|
||||
if(signature) *signature = sig;
|
||||
if(serial) *serial = sn;
|
||||
if(count) *count = cnt;
|
||||
if(button) *button = land_rover_v0_button_from_signature(sig);
|
||||
if(check) *check = key_bytes[7] & 0x07U;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_validate_frame(
|
||||
uint64_t key,
|
||||
uint16_t tail,
|
||||
bool extra_bit,
|
||||
bool* check_ok,
|
||||
bool* tail_ok) {
|
||||
uint8_t key_bytes[8];
|
||||
pp_u64_to_bytes_be(key, key_bytes);
|
||||
|
||||
const uint32_t count = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
|
||||
const uint8_t expected_check = land_rover_v0_calculate_check(count);
|
||||
const uint16_t expected_tail = land_rover_v0_calculate_tail(count);
|
||||
|
||||
const bool local_check_ok = ((key_bytes[7] & 0x78U) == 0U) &&
|
||||
((key_bytes[7] & 0x07U) == expected_check);
|
||||
const bool local_tail_ok = (tail == expected_tail) && extra_bit;
|
||||
|
||||
if(check_ok) *check_ok = local_check_ok;
|
||||
if(tail_ok) *tail_ok = local_tail_ok;
|
||||
|
||||
return local_check_ok && local_tail_ok;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_add_decoded_bit(SubGhzProtocolDecoderLandRoverV0* instance, bool bit) {
|
||||
if(instance->bit_count < 80U) {
|
||||
const uint8_t byte_index = instance->bit_count / 8U;
|
||||
const uint8_t bit_index = 7U - (instance->bit_count % 8U);
|
||||
if(bit) {
|
||||
instance->raw[byte_index] |= (uint8_t)(1U << bit_index);
|
||||
}
|
||||
} else if(instance->bit_count == 80U) {
|
||||
instance->extra_bit = bit;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_finish_frame(SubGhzProtocolDecoderLandRoverV0* instance) {
|
||||
const uint64_t key = pp_bytes_to_u64_be(instance->raw);
|
||||
const uint16_t tail = ((uint16_t)instance->raw[8] << 8) | instance->raw[9];
|
||||
|
||||
if(!land_rover_v0_validate_frame(
|
||||
key, tail, instance->extra_bit, &instance->check_ok, &instance->tail_ok)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->key = key;
|
||||
instance->tail = tail;
|
||||
|
||||
land_rover_v0_parse_key_fields(
|
||||
key,
|
||||
&instance->command_signature,
|
||||
&instance->serial,
|
||||
&instance->count,
|
||||
&instance->button,
|
||||
&instance->check);
|
||||
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.data_count_bit = subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_process_transition(
|
||||
SubGhzProtocolDecoderLandRoverV0* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(!instance->boundary_pad_skipped) {
|
||||
if(level && land_rover_v0_is_short(duration)) {
|
||||
instance->boundary_pad_skipped = true;
|
||||
return true;
|
||||
}
|
||||
instance->boundary_pad_skipped = true;
|
||||
}
|
||||
|
||||
if(instance->pending_short) {
|
||||
if(!instance->previous_bit && !level && land_rover_v0_is_short(duration)) {
|
||||
instance->pending_short = false;
|
||||
return land_rover_v0_add_decoded_bit(instance, false);
|
||||
} else if(instance->previous_bit && level && land_rover_v0_is_short(duration)) {
|
||||
instance->pending_short = false;
|
||||
return land_rover_v0_add_decoded_bit(instance, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!instance->previous_bit) {
|
||||
if(level && land_rover_v0_is_long(duration)) {
|
||||
instance->previous_bit = true;
|
||||
return land_rover_v0_add_decoded_bit(instance, true);
|
||||
} else if(level && land_rover_v0_is_short(duration)) {
|
||||
instance->pending_short = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!level && land_rover_v0_is_long(duration)) {
|
||||
instance->previous_bit = false;
|
||||
return land_rover_v0_add_decoded_bit(instance, false);
|
||||
} else if(!level && land_rover_v0_is_short(duration)) {
|
||||
instance->pending_short = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static bool land_rover_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderLandRoverV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(*index >= LAND_ROVER_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_encoder_add_bit(
|
||||
SubGhzProtocolEncoderLandRoverV0* instance,
|
||||
size_t* index,
|
||||
bool* previous_bit,
|
||||
bool bit) {
|
||||
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
|
||||
const uint32_t te_long = subghz_protocol_land_rover_v0_const.te_long;
|
||||
|
||||
if(!*previous_bit && !bit) {
|
||||
if(!land_rover_v0_encoder_add_level(instance, index, true, te_short) ||
|
||||
!land_rover_v0_encoder_add_level(instance, index, false, te_short)) {
|
||||
return false;
|
||||
}
|
||||
} else if(!*previous_bit && bit) {
|
||||
if(!land_rover_v0_encoder_add_level(instance, index, true, te_long)) {
|
||||
return false;
|
||||
}
|
||||
} else if(*previous_bit && !bit) {
|
||||
if(!land_rover_v0_encoder_add_level(instance, index, false, te_long)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(!land_rover_v0_encoder_add_level(instance, index, false, te_short) ||
|
||||
!land_rover_v0_encoder_add_level(instance, index, true, te_short)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*previous_bit = bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool land_rover_v0_build_upload(SubGhzProtocolEncoderLandRoverV0* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
size_t index = 0;
|
||||
const uint32_t te_short = subghz_protocol_land_rover_v0_const.te_short;
|
||||
|
||||
uint8_t key_bytes[8];
|
||||
pp_u64_to_bytes_be(instance->key, key_bytes);
|
||||
|
||||
for(uint16_t i = 0; i < LAND_ROVER_V0_PREAMBLE_PAIRS; i++) {
|
||||
if(!land_rover_v0_encoder_add_level(instance, &index, true, te_short) ||
|
||||
!land_rover_v0_encoder_add_level(instance, &index, false, te_short)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!land_rover_v0_encoder_add_level(instance, &index, true, LAND_ROVER_V0_SYNC_US) ||
|
||||
!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_SYNC_US) ||
|
||||
!land_rover_v0_encoder_add_level(instance, &index, true, te_short)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool previous_bit = true;
|
||||
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint8_t bit_index = 2; bit_index < 64; bit_index++) {
|
||||
const uint8_t byte_index = bit_index / 8U;
|
||||
const uint8_t bit_in_byte = 7U - (bit_index % 8U);
|
||||
const bool bit = (key_bytes[byte_index] >> bit_in_byte) & 1U;
|
||||
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
instance->tail = land_rover_v0_calculate_tail(instance->count);
|
||||
for(uint8_t bit_index = 0; bit_index < 16; bit_index++) {
|
||||
const bool bit = (instance->tail >> (15U - bit_index)) & 1U;
|
||||
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, bit)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!land_rover_v0_encoder_add_bit(instance, &index, &previous_bit, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!land_rover_v0_encoder_add_level(instance, &index, false, LAND_ROVER_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = index;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderLandRoverV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &land_rover_v0_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_land_rover_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_land_rover_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->preamble_count = 0;
|
||||
memset(instance->raw, 0, sizeof(instance->raw));
|
||||
instance->bit_count = 0;
|
||||
instance->extra_bit = false;
|
||||
instance->previous_bit = true;
|
||||
instance->boundary_pad_skipped = false;
|
||||
instance->pending_short = false;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case LandRoverV0DecoderStepReset:
|
||||
if(level && land_rover_v0_is_short(duration)) {
|
||||
instance->preamble_count = 0;
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
|
||||
}
|
||||
break;
|
||||
|
||||
case LandRoverV0DecoderStepPreambleLow:
|
||||
if(!level && land_rover_v0_is_short(duration)) {
|
||||
instance->preamble_count++;
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LandRoverV0DecoderStepPreambleHigh:
|
||||
if(level && land_rover_v0_is_short(duration)) {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepPreambleLow;
|
||||
} else if(
|
||||
level && land_rover_v0_is_sync(duration) &&
|
||||
instance->preamble_count >= LAND_ROVER_V0_MIN_PREAMBLE_PAIRS) {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepSyncLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LandRoverV0DecoderStepSyncLow:
|
||||
if(!level && land_rover_v0_is_sync(duration)) {
|
||||
memset(instance->raw, 0, sizeof(instance->raw));
|
||||
instance->bit_count = 0;
|
||||
instance->extra_bit = false;
|
||||
instance->previous_bit = true;
|
||||
instance->boundary_pad_skipped = false;
|
||||
instance->pending_short = false;
|
||||
land_rover_v0_add_decoded_bit(instance, true);
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LandRoverV0DecoderStepData:
|
||||
if(!land_rover_v0_process_transition(instance, level, duration)) {
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->bit_count == subghz_protocol_land_rover_v0_const.min_count_bit_for_found) {
|
||||
if(land_rover_v0_finish_frame(instance) && instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = LandRoverV0DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->key,
|
||||
.decode_count_bit = 64,
|
||||
};
|
||||
uint8_t hash = subghz_protocol_blocks_get_hash_data(&decoder, 9);
|
||||
hash ^= (uint8_t)(instance->tail >> 8);
|
||||
hash ^= (uint8_t)instance->tail;
|
||||
hash ^= instance->extra_bit ? 1U : 0U;
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint8_t key_bytes[8];
|
||||
pp_u64_to_bytes_be(instance->key, key_bytes);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, FF_KEY, key_bytes, sizeof(key_bytes));
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
|
||||
pp_flipper_update_or_insert_u32(
|
||||
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
|
||||
pp_flipper_update_or_insert_u32(
|
||||
flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, instance->extra_bit ? 1U : 0U);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint8_t key_bytes[8] = {0};
|
||||
bool have_key = false;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, FF_KEY, key_bytes, sizeof(key_bytes))) {
|
||||
instance->key = pp_bytes_to_u64_be(key_bytes);
|
||||
have_key = true;
|
||||
}
|
||||
|
||||
if(!have_key) {
|
||||
instance->key = instance->generic.data;
|
||||
pp_u64_to_bytes_be(instance->key, key_bytes);
|
||||
}
|
||||
|
||||
uint32_t temp = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_TAIL, &temp, 1)) {
|
||||
instance->tail = temp & 0xFFFFU;
|
||||
} else {
|
||||
const uint32_t count = ((uint32_t)key_bytes[6] << 1) | ((key_bytes[7] >> 7) & 1U);
|
||||
instance->tail = land_rover_v0_calculate_tail(count);
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, &temp, 1)) {
|
||||
instance->extra_bit = (temp & 1U) != 0;
|
||||
} else {
|
||||
instance->extra_bit = true;
|
||||
}
|
||||
|
||||
land_rover_v0_validate_frame(
|
||||
instance->key,
|
||||
instance->tail,
|
||||
instance->extra_bit,
|
||||
&instance->check_ok,
|
||||
&instance->tail_ok);
|
||||
land_rover_v0_parse_key_fields(
|
||||
instance->key,
|
||||
&instance->command_signature,
|
||||
&instance->serial,
|
||||
&instance->count,
|
||||
&instance->button,
|
||||
&instance->check);
|
||||
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.data_count_bit =
|
||||
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderLandRoverV0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%06lX Btn:%02X - %s\r\n"
|
||||
"BtnSig:%06lX\r\n"
|
||||
"Cnt:%05lX Chk:%02X [%s] Tail:%05lX [%s]\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(unsigned long long)instance->key,
|
||||
(unsigned long)instance->serial,
|
||||
instance->button,
|
||||
land_rover_v0_button_name(instance->button),
|
||||
(unsigned long)instance->command_signature,
|
||||
(unsigned long)instance->count,
|
||||
instance->check,
|
||||
instance->check_ok ? "OK" : "BAD",
|
||||
(unsigned long)(((instance->tail >> 15) & 1U) ? 0x1FFFFUL : 0x0FFFFUL),
|
||||
instance->tail_ok ? "OK" : "BAD");
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
|
||||
static uint32_t land_rover_v0_signature_from_button(uint8_t button) {
|
||||
switch(button) {
|
||||
case LAND_ROVER_V0_BTN_LOCK:
|
||||
return LAND_ROVER_V0_SIG_LOCK;
|
||||
case LAND_ROVER_V0_BTN_UNLOCK:
|
||||
return LAND_ROVER_V0_SIG_UNLOCK;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t land_rover_v0_build_key(uint32_t signature, uint32_t serial, uint32_t count) {
|
||||
uint8_t key_bytes[8] = {0};
|
||||
key_bytes[0] = (uint8_t)((signature >> 16) & 0xFFU);
|
||||
key_bytes[1] = (uint8_t)((signature >> 8) & 0xFFU);
|
||||
key_bytes[2] = (uint8_t)(signature & 0xFFU);
|
||||
key_bytes[3] = (uint8_t)((serial >> 16) & 0xFFU);
|
||||
key_bytes[4] = (uint8_t)((serial >> 8) & 0xFFU);
|
||||
key_bytes[5] = (uint8_t)(serial & 0xFFU);
|
||||
key_bytes[6] = (uint8_t)((count >> 1) & 0xFFU);
|
||||
|
||||
const bool counter_lsb = (count & 1U) != 0;
|
||||
const uint8_t check = land_rover_v0_calculate_check(count);
|
||||
key_bytes[7] = (counter_lsb ? 0x80U : 0x00U) | check;
|
||||
|
||||
return pp_bytes_to_u64_be(key_bytes);
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderLandRoverV0* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderLandRoverV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &land_rover_v0_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
pp_encoder_buffer_ensure(instance, LAND_ROVER_V0_UPLOAD_CAPACITY);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_land_rover_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
pp_encoder_free(context);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_land_rover_v0_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderLandRoverV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
do {
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus load_status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_land_rover_v0_const.min_count_bit_for_found);
|
||||
if(load_status != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->serial = instance->generic.serial & 0xFFFFFFU;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt & 0x1FFU;
|
||||
|
||||
uint8_t key_bytes[8] = {0};
|
||||
bool have_key = false;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, FF_KEY, key_bytes, sizeof(key_bytes))) {
|
||||
instance->key = pp_bytes_to_u64_be(key_bytes);
|
||||
have_key = true;
|
||||
}
|
||||
|
||||
if(have_key) {
|
||||
land_rover_v0_parse_key_fields(
|
||||
instance->key,
|
||||
&instance->command_signature,
|
||||
&instance->serial,
|
||||
&instance->count,
|
||||
&instance->button,
|
||||
&instance->check);
|
||||
}
|
||||
|
||||
uint32_t u32 = 0;
|
||||
bool have_button = false;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, FF_SERIAL, &u32, 1)) {
|
||||
instance->serial = u32 & 0xFFFFFFU;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, FF_CNT, &u32, 1)) {
|
||||
instance->count = u32 & 0x1FFU;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, FF_BTN, &u32, 1)) {
|
||||
instance->button = (uint8_t)u32;
|
||||
have_button = true;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, LAND_ROVER_V0_FF_BTNSIG, &u32, 1)) {
|
||||
instance->command_signature = u32 & 0xFFFFFFU;
|
||||
}
|
||||
|
||||
if(have_button) {
|
||||
const uint32_t signature = land_rover_v0_signature_from_button(instance->button);
|
||||
if(signature != 0U) {
|
||||
instance->command_signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->command_signature == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->key = land_rover_v0_build_key(
|
||||
instance->command_signature, instance->serial, instance->count);
|
||||
|
||||
pp_u64_to_bytes_be(instance->key, key_bytes);
|
||||
instance->tail = land_rover_v0_calculate_tail(instance->count);
|
||||
|
||||
land_rover_v0_parse_key_fields(
|
||||
instance->key,
|
||||
&instance->command_signature,
|
||||
&instance->serial,
|
||||
&instance->count,
|
||||
&instance->button,
|
||||
&instance->check);
|
||||
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.data_count_bit =
|
||||
subghz_protocol_land_rover_v0_const.min_count_bit_for_found;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
if(!land_rover_v0_build_upload(instance) || instance->encoder.size_upload == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, FF_KEY, key_bytes, sizeof(key_bytes));
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->serial);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->button);
|
||||
pp_flipper_update_or_insert_u32(
|
||||
flipper_format, LAND_ROVER_V0_FF_BTNSIG, instance->command_signature);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->count);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_CHECK, instance->check);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_TAIL, instance->tail);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, LAND_ROVER_V0_FF_EXTRA_BIT, 1U);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_land_rover_v0_stop(void* context) {
|
||||
pp_encoder_stop(context);
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context) {
|
||||
return pp_encoder_yield(context);
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,38 @@
|
||||
#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/level_duration.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define LAND_ROVER_PROTOCOL_V0_NAME "Land Rover V0"
|
||||
|
||||
extern const SubGhzProtocol land_rover_v0_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_land_rover_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_land_rover_v0_free(void* context);
|
||||
void subghz_protocol_decoder_land_rover_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_land_rover_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_land_rover_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_land_rover_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_land_rover_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_land_rover_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_land_rover_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_land_rover_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_land_rover_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_land_rover_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_land_rover_v0_yield(void* context);
|
||||
@@ -0,0 +1,599 @@
|
||||
#include "mazda_v0.h"
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mazda_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define MAZDA_V0_UPLOAD_CAPACITY (((12U + 3U + 8U + 1U) * 16U) + 2U)
|
||||
_Static_assert(
|
||||
MAZDA_V0_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"MAZDA_V0_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
#define MAZDA_V0_GAP_US 0xCB20
|
||||
#define MAZDA_V0_SYNC_BYTE 0xD7
|
||||
#define MAZDA_V0_TAIL_BYTE 0x5A
|
||||
#define MAZDA_V0_PREAMBLE_ONES 16
|
||||
|
||||
// =============================================================================
|
||||
// STRUCT DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMazdaV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t preamble_pattern;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolDecoderMazdaV0;
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderMazdaV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolEncoderMazdaV0;
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MazdaV0DecoderStepReset = 0,
|
||||
MazdaV0DecoderStepPreamble = 5,
|
||||
MazdaV0DecoderStepData = 6,
|
||||
} MazdaV0DecoderStep;
|
||||
|
||||
// =============================================================================
|
||||
// FUNCTION PROTOTYPES
|
||||
// =============================================================================
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event);
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic);
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter);
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value);
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance);
|
||||
#endif
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
uint8_t button);
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mazda_v0_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_mazda_v0_feed,
|
||||
.reset = subghz_protocol_decoder_mazda_v0_reset,
|
||||
.get_hash_data = pp_decoder_hash_blocks,
|
||||
.serialize = subghz_protocol_decoder_mazda_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_mazda_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_mazda_v0_get_string,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_mazda_v0_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_mazda_v0_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
const SubGhzProtocol mazda_v0_protocol = {
|
||||
.name = MAZDA_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_mazda_v0_decoder,
|
||||
.encoder = &subghz_protocol_mazda_v0_encoder,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// HELPERS
|
||||
// =============================================================================
|
||||
|
||||
static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
counter &= 0xFFFFFU;
|
||||
return (uint8_t)(((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) + ((serial >> 8) & 0xFF) +
|
||||
(serial & 0xFF) + ((counter >> 8) & 0xFF) + (counter & 0xFF) +
|
||||
((((counter >> 16) & 0x0F) | ((button & 0x0F) << 4)) & 0xFF));
|
||||
}
|
||||
|
||||
static const char* mazda_v0_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
case 0x04:
|
||||
return "Trunk";
|
||||
case 0x08:
|
||||
return "Remote";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event) {
|
||||
const uint32_t tol = (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U;
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < tol) {
|
||||
*event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_long) < tol) {
|
||||
*event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
|
||||
uint8_t data[8];
|
||||
pp_u64_to_bytes_be(generic->data, data);
|
||||
|
||||
const bool parity = subghz_protocol_blocks_parity8(data[7]) != 0;
|
||||
const uint8_t limit = parity ? 6 : 5;
|
||||
const uint8_t mask = data[limit];
|
||||
|
||||
for(uint8_t i = 0; i < limit; i++) {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
|
||||
if(!parity) {
|
||||
data[6] ^= mask;
|
||||
}
|
||||
|
||||
const uint8_t counter_lo = (data[5] & 0x55) | (data[6] & 0xAA);
|
||||
const uint8_t counter_mid = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
|
||||
generic->serial = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
|
||||
((uint32_t)data[2] << 8) | (uint32_t)data[3];
|
||||
generic->btn = (data[4] >> 4) & 0x0F;
|
||||
generic->cnt = (((uint32_t)data[4] & 0x0F) << 16) | ((uint32_t)counter_mid << 8) |
|
||||
(uint32_t)counter_lo;
|
||||
generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
uint8_t data[8];
|
||||
|
||||
counter &= 0xFFFFFU;
|
||||
button &= 0x0F;
|
||||
|
||||
data[0] = (serial >> 24) & 0xFF;
|
||||
data[1] = (serial >> 16) & 0xFF;
|
||||
data[2] = (serial >> 8) & 0xFF;
|
||||
data[3] = serial & 0xFF;
|
||||
data[4] = (button << 4) | ((counter >> 16) & 0x0F);
|
||||
data[5] = (counter >> 8) & 0xFF;
|
||||
data[6] = counter & 0xFF;
|
||||
data[7] = mazda_v0_calculate_checksum(serial, button, counter);
|
||||
|
||||
const uint8_t stored_5 = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
const uint8_t stored_6 = (data[6] & 0xAA) | (data[5] & 0x55);
|
||||
const uint8_t xor_mask = stored_5 ^ stored_6;
|
||||
const bool replace_second = subghz_protocol_blocks_parity8(data[7]) == 0;
|
||||
const uint8_t forward_mask = replace_second ? stored_5 : stored_6;
|
||||
|
||||
data[5] = replace_second ? stored_5 : xor_mask;
|
||||
data[6] = replace_second ? xor_mask : stored_6;
|
||||
|
||||
for(size_t i = 0; i < 5; i++) {
|
||||
data[i] ^= forward_mask;
|
||||
}
|
||||
|
||||
return pp_bytes_to_u64_be(data);
|
||||
}
|
||||
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t before = *index;
|
||||
*index = pp_emit(instance->encoder.upload, before, MAZDA_V0_UPLOAD_CAPACITY, level, duration);
|
||||
return *index > before;
|
||||
}
|
||||
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value) {
|
||||
if(*index + 16 > MAZDA_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
*index = pp_emit_byte_manchester(
|
||||
instance->encoder.upload,
|
||||
*index,
|
||||
MAZDA_V0_UPLOAD_CAPACITY,
|
||||
value,
|
||||
subghz_protocol_mazda_v0_const.te_short);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
size_t index = 0;
|
||||
const uint64_t key64 = instance->generic.data;
|
||||
|
||||
for(size_t r = 0; r < 12; r++) {
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, MAZDA_V0_SYNC_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int bi = 0; bi < 8; bi++) {
|
||||
const uint8_t raw = (uint8_t)((key64 >> (56 - bi * 8)) & 0xFF);
|
||||
const uint8_t air = (uint8_t)~raw;
|
||||
if(!mazda_v0_append_byte(instance, &index, air)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, MAZDA_V0_TAIL_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
uint8_t button) {
|
||||
return pp_write_display(flipper_format, protocol_name, mazda_v0_get_button_name(button));
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ENCODER
|
||||
// =============================================================================
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &mazda_v0_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
pp_encoder_buffer_ensure(instance, MAZDA_V0_UPLOAD_CAPACITY);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
do {
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
if(load_st != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
uint32_t serial = instance->generic.serial;
|
||||
uint32_t btn = instance->generic.btn;
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)btn & 0x0FU;
|
||||
instance->generic.cnt = cnt & 0xFFFFFU;
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 10);
|
||||
|
||||
instance->generic.data = mazda_v0_encode_key(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
instance->generic.data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(!mazda_v0_build_upload(instance)) {
|
||||
break;
|
||||
}
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
pp_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
if(!flipper_format_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data))) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// =============================================================================
|
||||
// DECODER
|
||||
// =============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &mazda_v0_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool data = false;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case MazdaV0DecoderStepReset:
|
||||
if(level && ((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) <
|
||||
(uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepPreamble;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepPreamble:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0);
|
||||
|
||||
if(data) {
|
||||
instance->preamble_count++;
|
||||
} else if(instance->preamble_count <= MAZDA_V0_PREAMBLE_ONES - 1U) {
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if((instance->preamble_pattern == MAZDA_V0_SYNC_BYTE) &&
|
||||
(instance->preamble_count > MAZDA_V0_PREAMBLE_ONES - 1U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepData:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, data);
|
||||
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found) {
|
||||
instance->generic.data = ~instance->decoder.decode_data;
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
if(mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt) ==
|
||||
(uint8_t)instance->generic.data) {
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->serial,
|
||||
instance->button,
|
||||
instance->count,
|
||||
0);
|
||||
|
||||
ret = mazda_v0_write_display(
|
||||
flipper_format, instance->generic.protocol_name, instance->button);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t serial = instance->serial;
|
||||
uint32_t btn = instance->button;
|
||||
uint32_t cnt = instance->count;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
instance->serial = serial;
|
||||
instance->button = (uint8_t)btn;
|
||||
instance->count = cnt;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
const uint8_t raw_crc = instance->generic.data & 0xFF;
|
||||
const uint8_t calc_crc = mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit CRC:%s\r\n"
|
||||
"Key: %016llX\r\n"
|
||||
"Sn: %08lX Btn: %02X - %s\r\n"
|
||||
"Cnt: %05lX Chk: %02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(raw_crc == calc_crc) ? "OK" : "BAD",
|
||||
(unsigned long long)instance->generic.data,
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
mazda_v0_get_button_name(instance->generic.btn),
|
||||
(unsigned long)(instance->generic.cnt & 0xFFFFFU),
|
||||
raw_crc);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#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/level_duration.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define MAZDA_PROTOCOL_V0_NAME "Mazda V0"
|
||||
|
||||
extern const SubGhzProtocol mazda_v0_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -0,0 +1,253 @@
|
||||
#include "mitsubishi_v0.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.h>
|
||||
|
||||
// Original implementation by @lupettohf
|
||||
|
||||
#define MITSUBISHI_BIT_COUNT 96
|
||||
#define MITSUBISHI_DATA_BYTES 12
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mitsubishi_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MitsubishiDecoderStepReset = 0,
|
||||
MitsubishiDecoderStepDataSave,
|
||||
MitsubishiDecoderStepDataCheck,
|
||||
} MitsubishiDecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderMitsubishi {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t decoder_state;
|
||||
uint16_t bit_count;
|
||||
uint8_t decode_data[MITSUBISHI_DATA_BYTES];
|
||||
};
|
||||
|
||||
static void mitsubishi_unscramble_payload(uint8_t* payload) {
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
payload[i] = (uint8_t)~payload[i];
|
||||
}
|
||||
|
||||
uint16_t counter = ((uint16_t)payload[4] << 8) | payload[5];
|
||||
uint8_t hi = (counter >> 8) & 0xFF;
|
||||
uint8_t lo = counter & 0xFF;
|
||||
uint8_t mask1 = (hi & 0xAAU) | (lo & 0x55U);
|
||||
uint8_t mask2 = (lo & 0xAAU) | (hi & 0x55U);
|
||||
uint8_t mask3 = mask1 ^ mask2;
|
||||
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
payload[i] ^= mask3;
|
||||
}
|
||||
}
|
||||
|
||||
static void mitsubishi_reset_payload(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, sizeof(instance->decode_data));
|
||||
}
|
||||
|
||||
static bool mitsubishi_collect_pair(
|
||||
SubGhzProtocolDecoderMitsubishi* instance,
|
||||
uint32_t high,
|
||||
uint32_t low) {
|
||||
bool bit_value;
|
||||
|
||||
if(pp_is_short(high, &subghz_protocol_mitsubishi_const) &&
|
||||
pp_is_long(low, &subghz_protocol_mitsubishi_const)) {
|
||||
bit_value = true;
|
||||
} else if(
|
||||
pp_is_long(high, &subghz_protocol_mitsubishi_const) &&
|
||||
pp_is_short(low, &subghz_protocol_mitsubishi_const)) {
|
||||
bit_value = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t bit_index = instance->bit_count;
|
||||
if(bit_index < MITSUBISHI_BIT_COUNT) {
|
||||
if(bit_value) {
|
||||
uint8_t byte_index = bit_index >> 3;
|
||||
uint8_t bit_position = 7 - (bit_index & 0x07);
|
||||
instance->decode_data[byte_index] |= (1U << bit_position);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mitsubishi_publish_frame(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
uint8_t payload[MITSUBISHI_DATA_BYTES];
|
||||
memcpy(payload, instance->decode_data, sizeof(payload));
|
||||
mitsubishi_unscramble_payload(payload);
|
||||
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
instance->generic.serial = ((uint32_t)payload[0] << 24) | ((uint32_t)payload[1] << 16) |
|
||||
((uint32_t)payload[2] << 8) | payload[3];
|
||||
instance->generic.cnt = ((uint16_t)payload[4] << 8) | payload[5];
|
||||
instance->generic.btn = payload[6];
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mitsubishi_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.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 mitsubishi_v0_protocol = {
|
||||
.name = MITSUBISHI_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_mitsubishi_decoder,
|
||||
.encoder = &subghz_protocol_mitsubishi_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &mitsubishi_v0_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
mitsubishi_reset_payload(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case MitsubishiDecoderStepReset:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder_state = MitsubishiDecoderStepDataCheck;
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepDataSave:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder_state = MitsubishiDecoderStepDataCheck;
|
||||
} else {
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
mitsubishi_reset_payload(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepDataCheck:
|
||||
if(!level) {
|
||||
if(mitsubishi_collect_pair(instance, instance->decoder.te_last, duration)) {
|
||||
if(instance->bit_count >= MITSUBISHI_BIT_COUNT) {
|
||||
mitsubishi_publish_frame(instance);
|
||||
mitsubishi_reset_payload(instance);
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder_state = MitsubishiDecoderStepDataSave;
|
||||
}
|
||||
} else {
|
||||
mitsubishi_reset_payload(instance);
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
uint8_t hash = 0;
|
||||
for(size_t i = 0; i < sizeof(instance->decode_data); i++) {
|
||||
hash ^= instance->decode_data[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mitsubishi_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t btn = instance->generic.btn;
|
||||
pp_encoder_read_fields(
|
||||
flipper_format, &instance->generic.serial, &btn, &instance->generic.cnt, NULL);
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Sn:%08lX Cnt:%04lX\r\n"
|
||||
"Btn:%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define MITSUBISHI_PROTOCOL_NAME "Mitsubishi V0"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMitsubishi SubGhzProtocolDecoderMitsubishi;
|
||||
|
||||
extern const SubGhzProtocol mitsubishi_v0_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_mitsubishi_free(void* context);
|
||||
void subghz_protocol_decoder_mitsubishi_reset(void* context);
|
||||
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "../protopirate_protocol_plugins.h"
|
||||
#include "../chrysler_v0.h"
|
||||
#include "../fiat_v0.h"
|
||||
#include "../fiat_v1.h"
|
||||
#include "../ford_v0.h"
|
||||
#include "../kia_v1.h"
|
||||
#include "../porsche_touareg.h"
|
||||
#include "../psa.h"
|
||||
#include "../subaru.h"
|
||||
#include "../vag.h"
|
||||
#include "../star_line.h"
|
||||
|
||||
static const SubGhzProtocol* const protopirate_protocol_registry_am_items[] = {
|
||||
&chrysler_protocol_v0,
|
||||
&fiat_protocol_v0,
|
||||
&fiat_v1_protocol,
|
||||
&ford_protocol_v0,
|
||||
&kia_protocol_v1,
|
||||
&porsche_touareg_protocol,
|
||||
&psa_protocol,
|
||||
&subaru_protocol,
|
||||
&vag_protocol,
|
||||
&subghz_protocol_star_line,
|
||||
};
|
||||
|
||||
static const SubGhzProtocolRegistry protopirate_protocol_registry_am = {
|
||||
.items = protopirate_protocol_registry_am_items,
|
||||
.size = sizeof(protopirate_protocol_registry_am_items) /
|
||||
sizeof(protopirate_protocol_registry_am_items[0]),
|
||||
};
|
||||
|
||||
static const ProtoPirateProtocolPlugin protopirate_am_plugin = {
|
||||
.plugin_name = "ProtoPirate AM Registry",
|
||||
.filter = ProtoPirateProtocolRegistryFilterAM,
|
||||
.registry = &protopirate_protocol_registry_am,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor protopirate_am_plugin_descriptor = {
|
||||
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
|
||||
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
|
||||
.entry_point = &protopirate_am_plugin,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* protopirate_am_plugin_ep(void) {
|
||||
return &protopirate_am_plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#include "../protopirate_protocol_plugins.h"
|
||||
#include "../scher_khan.h"
|
||||
#include "../kia_v0.h"
|
||||
#include "../kia_v2.h"
|
||||
#include "../kia_v3_v4.h"
|
||||
#include "../kia_v5.h"
|
||||
#include "../kia_v6.h"
|
||||
#include "../kia_v7.h"
|
||||
#include "../ford_v1.h"
|
||||
#include "../ford_v2.h"
|
||||
#include "../ford_v3.h"
|
||||
#include "../honda_static.h"
|
||||
#include "../land_rover_v0.h"
|
||||
#include "../mazda_v0.h"
|
||||
#include "../mitsubishi_v0.h"
|
||||
#include "../psa.h"
|
||||
|
||||
static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = {
|
||||
&subghz_protocol_scher_khan,
|
||||
&kia_protocol_v0,
|
||||
&kia_protocol_v2,
|
||||
&kia_protocol_v3_v4,
|
||||
&kia_protocol_v5,
|
||||
&kia_protocol_v6,
|
||||
&ford_protocol_v1,
|
||||
&ford_protocol_v2,
|
||||
&ford_protocol_v3,
|
||||
&honda_static_protocol,
|
||||
&land_rover_v0_protocol,
|
||||
&mazda_v0_protocol,
|
||||
&mitsubishi_v0_protocol,
|
||||
&kia_protocol_v7,
|
||||
&psa_protocol,
|
||||
};
|
||||
|
||||
static const SubGhzProtocolRegistry protopirate_protocol_registry_fm = {
|
||||
.items = protopirate_protocol_registry_fm_items,
|
||||
.size = sizeof(protopirate_protocol_registry_fm_items) /
|
||||
sizeof(protopirate_protocol_registry_fm_items[0]),
|
||||
};
|
||||
|
||||
static const ProtoPirateProtocolPlugin protopirate_fm_plugin = {
|
||||
.plugin_name = "ProtoPirate FM Registry",
|
||||
.filter = ProtoPirateProtocolRegistryFilterFM,
|
||||
.registry = &protopirate_protocol_registry_fm,
|
||||
};
|
||||
|
||||
static const FlipperAppPluginDescriptor protopirate_fm_plugin_descriptor = {
|
||||
.appid = PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID,
|
||||
.ep_api_version = PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION,
|
||||
.entry_point = &protopirate_fm_plugin,
|
||||
};
|
||||
|
||||
const FlipperAppPluginDescriptor* protopirate_fm_plugin_ep(void) {
|
||||
return &protopirate_fm_plugin_descriptor;
|
||||
}
|
||||
@@ -0,0 +1,370 @@
|
||||
#include "porsche_touareg.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.h>
|
||||
|
||||
// Original implementation by @lupettohf
|
||||
|
||||
#define PORSCHE_CAYENNE_BIT_COUNT 64
|
||||
#define PC_TE_SYNC 3370U
|
||||
#define PC_TE_GAP 5930U
|
||||
#define PC_SYNC_MIN 15
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_porsche_cayenne_const = {
|
||||
.te_short = 1680,
|
||||
.te_long = 3370,
|
||||
.te_delta = 500,
|
||||
.min_count_bit_for_found = PORSCHE_CAYENNE_BIT_COUNT,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
PorscheCayenneDecoderStepReset = 0,
|
||||
PorscheCayenneDecoderStepSync,
|
||||
PorscheCayenneDecoderStepGapHigh,
|
||||
PorscheCayenneDecoderStepGapLow,
|
||||
PorscheCayenneDecoderStepData,
|
||||
} PorscheCayenneDecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderPorscheCayenne {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t sync_count;
|
||||
uint64_t raw_data;
|
||||
uint8_t bit_count;
|
||||
};
|
||||
|
||||
static void porsche_cayenne_compute_frame(
|
||||
uint32_t serial24,
|
||||
uint8_t btn,
|
||||
uint16_t counter,
|
||||
uint8_t frame_type,
|
||||
uint8_t* pkt) {
|
||||
uint8_t b0 = (uint8_t)((btn << 4) | (frame_type & 0x07));
|
||||
uint8_t b1 = (serial24 >> 16) & 0xFF;
|
||||
uint8_t b2 = (serial24 >> 8) & 0xFF;
|
||||
uint8_t b3 = serial24 & 0xFF;
|
||||
|
||||
uint16_t cnt = counter + 1;
|
||||
uint8_t cnt_lo = cnt & 0xFF;
|
||||
uint8_t cnt_hi = (cnt >> 8) & 0xFF;
|
||||
|
||||
uint8_t r_h = b3;
|
||||
uint8_t r_m = b1;
|
||||
uint8_t r_l = b2;
|
||||
|
||||
#define ROTATE24(rh, rm, rl) \
|
||||
do { \
|
||||
uint8_t _ch = ((rh) >> 7) & 1U; \
|
||||
uint8_t _cm = ((rm) >> 7) & 1U; \
|
||||
uint8_t _cl = ((rl) >> 7) & 1U; \
|
||||
(rh) = (uint8_t)(((rh) << 1) | _cm); \
|
||||
(rm) = (uint8_t)(((rm) << 1) | _cl); \
|
||||
(rl) = (uint8_t)(((rl) << 1) | _ch); \
|
||||
} while(0)
|
||||
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
ROTATE24(r_h, r_m, r_l);
|
||||
}
|
||||
for(uint16_t i = 0; i < cnt_lo; i++) {
|
||||
ROTATE24(r_h, r_m, r_l);
|
||||
}
|
||||
|
||||
#undef ROTATE24
|
||||
|
||||
uint8_t a9a = r_h ^ b0;
|
||||
|
||||
uint8_t nb9b_p1 = (uint8_t)((~cnt_lo << 2) & 0xFC) ^ r_m;
|
||||
uint8_t nb9b_p2 = (uint8_t)((~cnt_hi << 2) & 0xFC) ^ r_m;
|
||||
uint8_t nb9b_p3 = (uint8_t)((~cnt_hi >> 6) & 0x03) ^ r_m;
|
||||
uint8_t a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03);
|
||||
|
||||
uint8_t nb9c_p1 = (uint8_t)((~cnt_lo >> 2) & 0x3F) ^ r_l;
|
||||
uint8_t nb9c_p2 = (uint8_t)((~cnt_hi & 0x03) << 6) ^ r_l;
|
||||
uint8_t nb9c_p3 = (uint8_t)((~cnt_hi >> 2) & 0x3F) ^ r_l;
|
||||
uint8_t a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C);
|
||||
|
||||
pkt[0] = b0;
|
||||
pkt[1] = b1;
|
||||
pkt[2] = b2;
|
||||
pkt[3] = b3;
|
||||
pkt[4] = (uint8_t)(((a9a >> 2) & 0x3F) | ((~cnt_lo & 0x03U) << 6));
|
||||
pkt[5] = (uint8_t)((~cnt_lo & 0xC0U) | ((a9a & 0x03U) << 4) | (a9b & 0x0CU) |
|
||||
((~cnt_lo >> 2) & 0x03U));
|
||||
pkt[6] = (uint8_t)(((a9b & 0x03U) << 6) | ((a9c >> 2) & 0x3CU) | ((~cnt_lo >> 4) & 0x03U));
|
||||
pkt[7] = (uint8_t)(((a9b >> 4) & 0x0FU) | ((a9c & 0x0FU) << 4));
|
||||
}
|
||||
|
||||
static void porsche_cayenne_parse_data(SubGhzProtocolDecoderPorscheCayenne* instance) {
|
||||
uint8_t pkt[8];
|
||||
uint64_t raw = instance->generic.data;
|
||||
|
||||
for(int8_t i = 7; i >= 0; i--) {
|
||||
pkt[i] = (uint8_t)(raw & 0xFF);
|
||||
raw >>= 8;
|
||||
}
|
||||
|
||||
instance->generic.serial = ((uint32_t)pkt[1] << 16) | ((uint32_t)pkt[2] << 8) | pkt[3];
|
||||
instance->generic.btn = (uint8_t)(pkt[0] >> 4);
|
||||
instance->generic.cnt = 0;
|
||||
|
||||
uint8_t frame_type = pkt[0] & 0x07;
|
||||
uint8_t try_pkt[8];
|
||||
for(uint16_t try_cnt = 1; try_cnt <= 256; try_cnt++) {
|
||||
porsche_cayenne_compute_frame(
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)(try_cnt - 1),
|
||||
frame_type,
|
||||
try_pkt);
|
||||
if(try_pkt[4] == pkt[4] && try_pkt[5] == pkt[5] && try_pkt[6] == pkt[6] &&
|
||||
try_pkt[7] == pkt[7]) {
|
||||
instance->generic.cnt = try_cnt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void porsche_cayenne_publish_frame(SubGhzProtocolDecoderPorscheCayenne* instance) {
|
||||
instance->generic.data = instance->raw_data;
|
||||
instance->generic.data_count_bit = PORSCHE_CAYENNE_BIT_COUNT;
|
||||
porsche_cayenne_parse_data(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_porsche_cayenne_decoder = {
|
||||
.alloc = subghz_protocol_decoder_porsche_cayenne_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.feed = subghz_protocol_decoder_porsche_cayenne_feed,
|
||||
.reset = subghz_protocol_decoder_porsche_cayenne_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_porsche_cayenne_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_porsche_cayenne_serialize,
|
||||
.deserialize = subghz_protocol_decoder_porsche_cayenne_deserialize,
|
||||
.get_string = subghz_protocol_decoder_porsche_cayenne_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_porsche_cayenne_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol porsche_touareg_protocol = {
|
||||
.name = PORSCHE_CAYENNE_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_porsche_cayenne_decoder,
|
||||
.encoder = &subghz_protocol_porsche_cayenne_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderPorscheCayenne));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &porsche_touareg_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->sync_count = 0;
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
const uint32_t te_short = subghz_protocol_porsche_cayenne_const.te_short;
|
||||
const uint32_t te_long = subghz_protocol_porsche_cayenne_const.te_long;
|
||||
const uint32_t te_delta = subghz_protocol_porsche_cayenne_const.te_delta;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case PorscheCayenneDecoderStepReset:
|
||||
if(!level && DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
instance->sync_count = 1;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepSync;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepSync:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
// keep collecting sync pairs
|
||||
} else if(
|
||||
instance->sync_count >= PC_SYNC_MIN &&
|
||||
DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepGapLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
instance->sync_count++;
|
||||
} else if(
|
||||
instance->sync_count >= PC_SYNC_MIN &&
|
||||
DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepGapHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepGapHigh:
|
||||
if(level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepGapLow:
|
||||
if(!level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepData:
|
||||
if(level) {
|
||||
bool bit_value = false;
|
||||
if(DURATION_DIFF(instance->decoder.te_last, te_short) < te_delta &&
|
||||
DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
bit_value = false;
|
||||
} else if(
|
||||
DURATION_DIFF(instance->decoder.te_last, te_long) < te_delta &&
|
||||
DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
bit_value = true;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->raw_data = (instance->raw_data << 1) | (bit_value ? 1U : 0U);
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count >= PORSCHE_CAYENNE_BIT_COUNT) {
|
||||
porsche_cayenne_publish_frame(instance);
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial & 0xFFFFFF,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_porsche_cayenne_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
porsche_cayenne_parse_data(instance);
|
||||
|
||||
uint32_t serial = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, FF_SERIAL, &serial, 1)) {
|
||||
instance->generic.serial = serial & 0xFFFFFF;
|
||||
}
|
||||
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, FF_CNT, &cnt, 1)) {
|
||||
instance->generic.cnt = cnt;
|
||||
}
|
||||
|
||||
uint32_t btn = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, FF_BTN, &btn, 1)) {
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
uint8_t frame_type = (uint8_t)((instance->generic.data >> 56) & 0x07);
|
||||
const char* frame_type_name = "??";
|
||||
if(frame_type == 0x02) {
|
||||
frame_type_name = "First";
|
||||
} else if(frame_type == 0x01) {
|
||||
frame_type_name = "Cont";
|
||||
} else if(frame_type == 0x04) {
|
||||
frame_type_name = "Final";
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Sn:%06lX Btn:%X\r\n"
|
||||
"Cnt:%04lX FT:%s\r\n"
|
||||
"Raw:%08lX%08lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(unsigned long)(instance->generic.serial & 0xFFFFFF),
|
||||
(unsigned int)instance->generic.btn,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
frame_type_name,
|
||||
(unsigned long)(instance->generic.data >> 32),
|
||||
(unsigned long)(instance->generic.data & 0xFFFFFFFF));
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define PORSCHE_CAYENNE_PROTOCOL_NAME "Porsche Touareg"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPorscheCayenne SubGhzProtocolDecoderPorscheCayenne;
|
||||
|
||||
extern const SubGhzProtocol porsche_touareg_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_porsche_cayenne_free(void* context);
|
||||
void subghz_protocol_decoder_porsche_cayenne_reset(void* context);
|
||||
void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output);
|
||||
@@ -0,0 +1,306 @@
|
||||
#include "protocol_items.h"
|
||||
#include <furi.h>
|
||||
#ifdef ENABLE_TIMING_TUNER_SCENE
|
||||
#include <string.h>
|
||||
#endif
|
||||
|
||||
#define TAG "ProtoPirateRegistry"
|
||||
|
||||
#define PROTOPIRATE_CC1101_REG_MDMCFG2 0x12U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_MASK 0x70U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_2FSK 0x00U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_GFSK 0x10U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK 0x30U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_4FSK 0x40U
|
||||
#define PROTOPIRATE_CC1101_MOD_FORMAT_MSK 0x70U
|
||||
|
||||
static bool protopirate_preset_try_get_register(
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size,
|
||||
uint8_t reg,
|
||||
uint8_t* value) {
|
||||
if(!preset_data || !value || (preset_data_size < 2U)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i + 1U < preset_data_size; i += 2U) {
|
||||
const uint8_t address = preset_data[i];
|
||||
const uint8_t data = preset_data[i + 1U];
|
||||
|
||||
if((address == 0x00U) && (data == 0x00U)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(address == reg) {
|
||||
*value = data;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size) {
|
||||
uint8_t mdmcfg2 = 0U;
|
||||
|
||||
if(!protopirate_preset_try_get_register(
|
||||
preset_data, preset_data_size, PROTOPIRATE_CC1101_REG_MDMCFG2, &mdmcfg2)) {
|
||||
FURI_LOG_W(TAG, "Preset missing MDMCFG2, defaulting to AM registry");
|
||||
return ProtoPirateProtocolRegistryFilterAM;
|
||||
}
|
||||
|
||||
// MDMCFG2[6:4] stores the CC1101 modulation format.
|
||||
// ASK/OOK maps to our AM decoder set; the FSK-family formats map to FM.
|
||||
switch(mdmcfg2 & PROTOPIRATE_CC1101_MOD_FORMAT_MASK) {
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_ASK_OOK:
|
||||
return ProtoPirateProtocolRegistryFilterAM;
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_2FSK:
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_GFSK:
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_4FSK:
|
||||
case PROTOPIRATE_CC1101_MOD_FORMAT_MSK:
|
||||
return ProtoPirateProtocolRegistryFilterFM;
|
||||
default:
|
||||
FURI_LOG_W(TAG, "Unknown MDMCFG2 0x%02X, defaulting to AM registry", mdmcfg2);
|
||||
return ProtoPirateProtocolRegistryFilterAM;
|
||||
}
|
||||
}
|
||||
|
||||
const char*
|
||||
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter) {
|
||||
return (filter == ProtoPirateProtocolRegistryFilterFM) ? "FM" : "AM";
|
||||
}
|
||||
|
||||
#ifdef ENABLE_TIMING_TUNER_SCENE
|
||||
// Protocol timing definitions - mirrors the SubGhzBlockConst in each protocol
|
||||
static const ProtoPirateProtocolTiming protocol_timings[] = {
|
||||
// Honda Static
|
||||
{
|
||||
.name = HONDA_STATIC_PROTOCOL_NAME,
|
||||
.te_short = 63,
|
||||
.te_long = 700,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Kia V0: PWM 250/500µs — Kia 61bit, Suzuki 64bit, Honda V0 72bit
|
||||
{
|
||||
.name = "Kia V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 61,
|
||||
},
|
||||
// Kia V1: OOK PCM 800µs timing
|
||||
{
|
||||
.name = "Kia V1",
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit = 56,
|
||||
},
|
||||
// Kia V2: Manchester 500/1000µs
|
||||
{
|
||||
.name = "Kia V2",
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 51,
|
||||
},
|
||||
// Kia V3/V4: PWM 400/800µs
|
||||
{
|
||||
.name = "Kia V3/V4",
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Kia V5: PWM 400/800µs (same as V3/V4)
|
||||
{
|
||||
.name = "Kia V5",
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Kia V6: Manchester 200/400µs
|
||||
{
|
||||
.name = "Kia V6",
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 144,
|
||||
},
|
||||
// Kia V7: Manchester 250/500µs
|
||||
{
|
||||
.name = KIA_PROTOCOL_V7_NAME,
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Ford V0: Manchester 250/500µs
|
||||
{
|
||||
.name = "Ford V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Chrysler V0: PWM short/long
|
||||
{
|
||||
.name = "Chrysler V0",
|
||||
.te_short = 300,
|
||||
.te_long = 3700,
|
||||
.te_delta = 400,
|
||||
.min_count_bit = 80,
|
||||
},
|
||||
// Ford V1: Manchester 65/130us
|
||||
{
|
||||
.name = FORD_PROTOCOL_V1_NAME,
|
||||
.te_short = 65,
|
||||
.te_long = 130,
|
||||
.te_delta = 39,
|
||||
.min_count_bit = 136,
|
||||
},
|
||||
// Ford V2: Manchester 200/400us
|
||||
{
|
||||
.name = FORD_PROTOCOL_V2_NAME,
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 260,
|
||||
.min_count_bit = 104,
|
||||
},
|
||||
// Ford V3: Manchester 240/480us
|
||||
{
|
||||
.name = FORD_PROTOCOL_V3_NAME,
|
||||
.te_short = 240,
|
||||
.te_long = 480,
|
||||
.te_delta = 60,
|
||||
.min_count_bit = 104,
|
||||
},
|
||||
// Fiat V0: Manchester 200/400µs
|
||||
{
|
||||
.name = "Fiat V0",
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Fiat V1: Manchester dynamic (baseline Type A 260/520us)
|
||||
{
|
||||
.name = "Fiat V1",
|
||||
.te_short = 260,
|
||||
.te_long = 520,
|
||||
.te_delta = 80,
|
||||
.min_count_bit = 80,
|
||||
},
|
||||
// Mazda V0: 250/500us
|
||||
{
|
||||
.name = "Mazda V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Land Rover V0: Differential PWM 250/500us + sync 750us
|
||||
{
|
||||
.name = LAND_ROVER_PROTOCOL_V0_NAME,
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 81,
|
||||
},
|
||||
// Porsche Touareg: 1680/3370us
|
||||
{
|
||||
.name = "Porsche Touareg",
|
||||
.te_short = 1680,
|
||||
.te_long = 3370,
|
||||
.te_delta = 500,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Subaru: PPM 800/1600µs
|
||||
{
|
||||
.name = "Subaru",
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// VW: Manchester 500/1000µs
|
||||
{
|
||||
.name = "VW",
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 80,
|
||||
},
|
||||
// Scher-Khan: PWM 750/1100µs
|
||||
{
|
||||
.name = "Scher-Khan",
|
||||
.te_short = 750,
|
||||
.te_long = 1100,
|
||||
.te_delta = 180,
|
||||
.min_count_bit = 35,
|
||||
},
|
||||
// Star Line: PWM 250/500µs
|
||||
{
|
||||
.name = "Star Line",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// PSA: Manchester 250/500µs (Pattern 1) or 125/250µs (Pattern 2)
|
||||
{
|
||||
.name = "PSA",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 128,
|
||||
},
|
||||
};
|
||||
|
||||
static const size_t protocol_timings_count = COUNT_OF(protocol_timings);
|
||||
|
||||
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name) {
|
||||
if(!protocol_name) return NULL;
|
||||
|
||||
for(size_t i = 0; i < protocol_timings_count; i++) {
|
||||
// Check for exact match or if the protocol name contains our timing name
|
||||
if(strcmp(protocol_name, protocol_timings[i].name) == 0 ||
|
||||
strstr(protocol_name, protocol_timings[i].name) != NULL) {
|
||||
return &protocol_timings[i];
|
||||
}
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char* alias;
|
||||
const char* canonical;
|
||||
} aliases[] = {
|
||||
{"Honda V0", "Kia V0"},
|
||||
{"Suzuki", "Kia V0"},
|
||||
{"V3", "Kia V3/V4"},
|
||||
{"V4", "Kia V3/V4"},
|
||||
};
|
||||
for(size_t a = 0; a < COUNT_OF(aliases); a++) {
|
||||
if(strstr(protocol_name, aliases[a].alias) == NULL) continue;
|
||||
for(size_t i = 0; i < protocol_timings_count; i++) {
|
||||
if(strstr(protocol_timings[i].name, aliases[a].canonical) != NULL) {
|
||||
return &protocol_timings[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index) {
|
||||
if(index >= protocol_timings_count) return NULL;
|
||||
return &protocol_timings[index];
|
||||
}
|
||||
|
||||
size_t protopirate_get_protocol_timing_count(void) {
|
||||
return protocol_timings_count;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
// protocols/protocol_items.h
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/types.h>
|
||||
|
||||
#include "kia_generic.h"
|
||||
#include "scher_khan.h"
|
||||
#include "kia_v0.h"
|
||||
#include "kia_v1.h"
|
||||
#include "kia_v2.h"
|
||||
#include "kia_v3_v4.h"
|
||||
#include "kia_v5.h"
|
||||
#include "kia_v6.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v0.h"
|
||||
#include "ford_v1.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
#include "chrysler_v0.h"
|
||||
#include "fiat_v0.h"
|
||||
#include "fiat_v1.h"
|
||||
#include "land_rover_v0.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "porsche_touareg.h"
|
||||
#include "subaru.h"
|
||||
#include "vag.h"
|
||||
#include "star_line.h"
|
||||
#include "psa.h"
|
||||
#include "honda_static.h"
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateProtocolRegistryFilterAM = 0,
|
||||
ProtoPirateProtocolRegistryFilterFM,
|
||||
} ProtoPirateProtocolRegistryFilter;
|
||||
|
||||
ProtoPirateProtocolRegistryFilter protopirate_get_protocol_registry_filter_for_preset(
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size);
|
||||
|
||||
const char*
|
||||
protopirate_get_protocol_registry_filter_name(ProtoPirateProtocolRegistryFilter filter);
|
||||
|
||||
#ifdef ENABLE_TIMING_TUNER_SCENE
|
||||
// Timing information for protocol analysis
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint32_t te_short;
|
||||
uint32_t te_long;
|
||||
uint32_t te_delta;
|
||||
uint32_t min_count_bit;
|
||||
} ProtoPirateProtocolTiming;
|
||||
|
||||
// Get timing info for a protocol by name (returns NULL if not found)
|
||||
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* protocol_name);
|
||||
|
||||
// Get timing info by index (for iteration)
|
||||
const ProtoPirateProtocolTiming* protopirate_get_protocol_timing_by_index(size_t index);
|
||||
|
||||
// Get number of protocols with timing info
|
||||
size_t protopirate_get_protocol_timing_count(void);
|
||||
#endif
|
||||
@@ -0,0 +1,342 @@
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
const char FF_BIT[] = "Bit";
|
||||
const char FF_KEY[] = "Key";
|
||||
const char FF_SERIAL[] = "Serial";
|
||||
const char FF_BTN[] = "Btn";
|
||||
const char FF_CNT[] = "Cnt";
|
||||
const char FF_REPEAT[] = "Repeat";
|
||||
const char FF_PROTOCOL[] = "Protocol";
|
||||
const char FF_PRESET[] = "Preset";
|
||||
const char FF_FREQUENCY[] = "Frequency";
|
||||
const char FF_MANUFACTURE[] = "Manufacture";
|
||||
const char FF_TYPE[] = "Type";
|
||||
|
||||
uint8_t pp_reverse_bits8(uint8_t value) {
|
||||
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
|
||||
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
|
||||
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
|
||||
return value;
|
||||
}
|
||||
|
||||
void pp_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[i] = (uint8_t)((data >> ((7U - i) * 8U)) & 0xFFU);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t pp_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
data = (data << 8U) | bytes[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool pp_hex_nibble(char c, uint8_t* nibble) {
|
||||
if(c >= '0' && c <= '9') {
|
||||
*nibble = (uint8_t)(c - '0');
|
||||
} else if(c >= 'A' && c <= 'F') {
|
||||
*nibble = (uint8_t)(c - 'A' + 10);
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
*nibble = (uint8_t)(c - 'a' + 10);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pp_parse_hex_u64_strict(const char* str, uint64_t* out_key) {
|
||||
if(!str || !out_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t key = 0;
|
||||
uint8_t hex_pos = 0;
|
||||
for(size_t i = 0; str[i] != '\0' && hex_pos < 16; i++) {
|
||||
if(str[i] == ' ') {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t nibble = 0;
|
||||
if(!pp_hex_nibble(str[i], &nibble)) {
|
||||
return false;
|
||||
}
|
||||
key = (key << 4) | nibble;
|
||||
hex_pos++;
|
||||
}
|
||||
|
||||
if(hex_pos != 16) {
|
||||
return false;
|
||||
}
|
||||
*out_key = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pp_flipper_read_hex_u64(FlipperFormat* flipper_format, const char* key, uint64_t* out_key) {
|
||||
FuriString* value = furi_string_alloc();
|
||||
if(!value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
if(flipper_format_rewind(flipper_format) &&
|
||||
flipper_format_read_string(flipper_format, key, value)) {
|
||||
ok = pp_parse_hex_u64_strict(furi_string_get_cstr(value), out_key);
|
||||
}
|
||||
furi_string_free(value);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void pp_flipper_update_or_insert_u32(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t value) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_uint32(flipper_format, key, &value, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, key, &value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus pp_verify_protocol_name(FlipperFormat* ff, const char* expected_name) {
|
||||
if(!ff || !expected_name) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
if(!tmp) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
SubGhzProtocolStatus result = SubGhzProtocolStatusError;
|
||||
if(!flipper_format_read_string(ff, FF_PROTOCOL, tmp)) {
|
||||
result = SubGhzProtocolStatusErrorParserOthers;
|
||||
} else if(furi_string_equal(tmp, expected_name)) {
|
||||
result = SubGhzProtocolStatusOk;
|
||||
}
|
||||
furi_string_free(tmp);
|
||||
return result;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus pp_encoder_read_bit(
|
||||
FlipperFormat* ff,
|
||||
const uint16_t* allowed_bits,
|
||||
size_t allowed_bits_count,
|
||||
uint32_t* out_bit) {
|
||||
if(!ff || !out_bit) return SubGhzProtocolStatusError;
|
||||
flipper_format_rewind(ff);
|
||||
uint32_t bit = 0;
|
||||
if(!flipper_format_read_uint32(ff, FF_BIT, &bit, 1)) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
if(allowed_bits && allowed_bits_count) {
|
||||
bool ok = false;
|
||||
for(size_t i = 0; i < allowed_bits_count; i++) {
|
||||
if((uint32_t)allowed_bits[i] == bit) {
|
||||
ok = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!ok) return SubGhzProtocolStatusError;
|
||||
}
|
||||
*out_bit = bit;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void pp_encoder_read_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t* serial_out,
|
||||
uint32_t* btn_out,
|
||||
uint32_t* cnt_out,
|
||||
uint32_t* type_out) {
|
||||
if(!ff) return;
|
||||
uint32_t tmp = 0;
|
||||
if(serial_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, FF_SERIAL, &tmp, 1)) *serial_out = tmp;
|
||||
}
|
||||
if(btn_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, FF_BTN, &tmp, 1)) *btn_out = tmp;
|
||||
}
|
||||
if(cnt_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, FF_CNT, &tmp, 1)) *cnt_out = tmp;
|
||||
}
|
||||
if(type_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, FF_TYPE, &tmp, 1)) *type_out = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t pp_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat) {
|
||||
if(!ff) return default_repeat;
|
||||
flipper_format_rewind(ff);
|
||||
uint32_t tmp = 0;
|
||||
return flipper_format_read_uint32(ff, FF_REPEAT, &tmp, 1) ? tmp : default_repeat;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus pp_serialize_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t field_mask,
|
||||
uint32_t serial,
|
||||
uint32_t btn,
|
||||
uint32_t cnt,
|
||||
uint32_t type) {
|
||||
if(!ff) return SubGhzProtocolStatusError;
|
||||
|
||||
if((field_mask & PP_FIELD_SERIAL) && !flipper_format_write_uint32(ff, FF_SERIAL, &serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & PP_FIELD_BTN) && !flipper_format_write_uint32(ff, FF_BTN, &btn, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & PP_FIELD_CNT) && !flipper_format_write_uint32(ff, FF_CNT, &cnt, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & PP_FIELD_TYPE) && !flipper_format_write_uint32(ff, FF_TYPE, &type, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
pp_write_display(FlipperFormat* ff, const char* protocol_name, const char* suffix) {
|
||||
if(!ff || !protocol_name || !suffix) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
FuriString* display = furi_string_alloc();
|
||||
if(!display) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
furi_string_printf(display, "%s - %s", protocol_name, suffix);
|
||||
SubGhzProtocolStatus status =
|
||||
flipper_format_write_string_cstr(ff, "Disp", furi_string_get_cstr(display)) ?
|
||||
SubGhzProtocolStatusOk :
|
||||
SubGhzProtocolStatusErrorParserOthers;
|
||||
furi_string_free(display);
|
||||
return status;
|
||||
}
|
||||
|
||||
size_t pp_emit_merge(LevelDuration* up, size_t i, size_t cap, bool level, uint32_t us) {
|
||||
if(i > 0 && level_duration_get_level(up[i - 1]) == level) {
|
||||
uint32_t prev = level_duration_get_duration(up[i - 1]);
|
||||
up[i - 1] = level_duration_make(level, prev + us);
|
||||
return i;
|
||||
}
|
||||
if(i < cap) up[i++] = level_duration_make(level, us);
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t
|
||||
pp_emit_byte_manchester(LevelDuration* up, size_t i, size_t cap, uint8_t value, uint32_t te) {
|
||||
for(int8_t bit = 7; bit >= 0; bit--) {
|
||||
bool bit_value = ((value >> bit) & 1) != 0;
|
||||
i = pp_emit_manchester_bit(up, i, cap, bit_value, te);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t
|
||||
pp_emit_short_pairs(LevelDuration* up, size_t i, size_t cap, uint32_t te, size_t pair_count) {
|
||||
for(size_t p = 0; p < pair_count; p++) {
|
||||
i = pp_emit(up, i, cap, true, te);
|
||||
i = pp_emit(up, i, cap, false, te);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void pp_encoder_free(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateEncoderHeader* hdr = context;
|
||||
hdr->encoder.upload = NULL;
|
||||
hdr->encoder.size_upload = 0;
|
||||
free(hdr);
|
||||
}
|
||||
|
||||
void pp_encoder_stop(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateEncoderHeader* hdr = context;
|
||||
hdr->encoder.is_running = false;
|
||||
hdr->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration pp_encoder_yield(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateEncoderHeader* hdr = context;
|
||||
if(hdr->encoder.repeat == 0 || !hdr->encoder.is_running || hdr->encoder.size_upload == 0) {
|
||||
hdr->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
LevelDuration ret = hdr->encoder.upload[hdr->encoder.front];
|
||||
if(++hdr->encoder.front == hdr->encoder.size_upload) {
|
||||
hdr->encoder.repeat--;
|
||||
hdr->encoder.front = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static LevelDuration* pp_shared_upload_buf = NULL;
|
||||
|
||||
LevelDuration* pp_shared_upload_buffer(void) {
|
||||
if(pp_shared_upload_buf == NULL) {
|
||||
pp_shared_upload_buf = malloc(PP_SHARED_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(pp_shared_upload_buf);
|
||||
}
|
||||
return pp_shared_upload_buf;
|
||||
}
|
||||
|
||||
size_t pp_shared_upload_capacity(void) {
|
||||
return PP_SHARED_UPLOAD_CAPACITY;
|
||||
}
|
||||
|
||||
void pp_shared_upload_release(void) {
|
||||
free(pp_shared_upload_buf);
|
||||
pp_shared_upload_buf = NULL;
|
||||
}
|
||||
|
||||
void pp_encoder_buffer_ensure(void* context, size_t capacity) {
|
||||
furi_check(context);
|
||||
ProtoPirateEncoderHeader* hdr = context;
|
||||
furi_check(capacity <= PP_SHARED_UPLOAD_CAPACITY);
|
||||
hdr->encoder.upload = pp_shared_upload_buffer();
|
||||
hdr->encoder.size_upload = capacity;
|
||||
}
|
||||
|
||||
uint8_t pp_decoder_hash_blocks(void* context) {
|
||||
furi_check(context);
|
||||
ProtoPirateDecoderHeader* hdr = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&hdr->decoder, (hdr->decoder.decode_count_bit / 8U) + 1U);
|
||||
}
|
||||
|
||||
void pp_decoder_free_default(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
bool pp_preset_name_is_custom_marker(const char* preset_name) {
|
||||
return preset_name && (!strcmp(preset_name, "Custom") || !strcmp(preset_name, "CUSTOM") ||
|
||||
!strcmp(preset_name, "FuriHalSubGhzPresetCustom") ||
|
||||
strstr(preset_name, "PresetCustom"));
|
||||
}
|
||||
|
||||
const char* pp_get_short_preset_name(const char* preset_name) {
|
||||
if(!preset_name || preset_name[0] == '\0') return "AM650";
|
||||
|
||||
if(strstr(preset_name, "Ook650") || strstr(preset_name, "OOK650")) return "AM650";
|
||||
if(strstr(preset_name, "Ook270") || strstr(preset_name, "OOK270")) return "AM270";
|
||||
if(strstr(preset_name, "2FSKDev238") || strstr(preset_name, "Dev238")) return "FM238";
|
||||
if(strstr(preset_name, "2FSKDev12K") || strstr(preset_name, "Dev12K")) return "FM12K";
|
||||
if(strstr(preset_name, "2FSKDev476") || strstr(preset_name, "Dev476")) return "FM476";
|
||||
if(pp_preset_name_is_custom_marker(preset_name)) return "Custom";
|
||||
|
||||
if(!strcmp(preset_name, "AM650")) return "AM650";
|
||||
if(!strcmp(preset_name, "AM270")) return "AM270";
|
||||
if(!strcmp(preset_name, "FM238")) return "FM238";
|
||||
if(!strcmp(preset_name, "FM12K")) return "FM12K";
|
||||
if(!strcmp(preset_name, "FM476")) return "FM476";
|
||||
|
||||
return preset_name;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/flipper_format/flipper_format.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/subghz/protocols/base.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
extern const char FF_BIT[];
|
||||
extern const char FF_KEY[];
|
||||
extern const char FF_SERIAL[];
|
||||
extern const char FF_BTN[];
|
||||
extern const char FF_CNT[];
|
||||
extern const char FF_REPEAT[];
|
||||
extern const char FF_PROTOCOL[];
|
||||
extern const char FF_PRESET[];
|
||||
extern const char FF_FREQUENCY[];
|
||||
extern const char FF_MANUFACTURE[];
|
||||
extern const char FF_TYPE[];
|
||||
|
||||
bool pp_preset_name_is_custom_marker(const char* preset_name);
|
||||
|
||||
const char* pp_get_short_preset_name(const char* preset_name);
|
||||
bool pp_parse_hex_u64_strict(const char* str, uint64_t* out_key);
|
||||
bool pp_flipper_read_hex_u64(FlipperFormat* flipper_format, const char* key, uint64_t* out_key);
|
||||
void pp_flipper_update_or_insert_u32(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t value);
|
||||
|
||||
SubGhzProtocolStatus pp_verify_protocol_name(FlipperFormat* ff, const char* expected_name);
|
||||
|
||||
#define PP_FIELD_SERIAL 0x01U
|
||||
#define PP_FIELD_BTN 0x02U
|
||||
#define PP_FIELD_CNT 0x04U
|
||||
#define PP_FIELD_TYPE 0x08U
|
||||
|
||||
SubGhzProtocolStatus pp_encoder_read_bit(
|
||||
FlipperFormat* ff,
|
||||
const uint16_t* allowed_bits,
|
||||
size_t allowed_bits_count,
|
||||
uint32_t* out_bit);
|
||||
|
||||
void pp_encoder_read_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t* serial_out,
|
||||
uint32_t* btn_out,
|
||||
uint32_t* cnt_out,
|
||||
uint32_t* type_out);
|
||||
|
||||
uint32_t pp_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat);
|
||||
|
||||
SubGhzProtocolStatus pp_serialize_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t field_mask,
|
||||
uint32_t serial,
|
||||
uint32_t btn,
|
||||
uint32_t cnt,
|
||||
uint32_t type);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
pp_write_display(FlipperFormat* ff, const char* protocol_name, const char* suffix);
|
||||
|
||||
static inline size_t pp_emit(LevelDuration* up, size_t i, size_t cap, bool level, uint32_t us) {
|
||||
if(i < cap) up[i++] = level_duration_make(level, us);
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t pp_emit_merge(LevelDuration* up, size_t i, size_t cap, bool level, uint32_t us);
|
||||
|
||||
static inline size_t
|
||||
pp_emit_manchester_bit(LevelDuration* up, size_t i, size_t cap, bool bit_value, uint32_t te) {
|
||||
i = pp_emit(up, i, cap, bit_value, te);
|
||||
i = pp_emit(up, i, cap, !bit_value, te);
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t
|
||||
pp_emit_byte_manchester(LevelDuration* up, size_t i, size_t cap, uint8_t value, uint32_t te);
|
||||
|
||||
size_t
|
||||
pp_emit_short_pairs(LevelDuration* up, size_t i, size_t cap, uint32_t te, size_t pair_count);
|
||||
|
||||
uint8_t pp_reverse_bits8(uint8_t value);
|
||||
void pp_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]);
|
||||
uint64_t pp_bytes_to_u64_be(const uint8_t bytes[8]);
|
||||
|
||||
static inline bool pp_is_short(uint32_t duration, const SubGhzBlockConst* t) {
|
||||
return DURATION_DIFF(duration, t->te_short) < t->te_delta;
|
||||
}
|
||||
|
||||
static inline bool pp_is_long(uint32_t duration, const SubGhzBlockConst* t) {
|
||||
return DURATION_DIFF(duration, t->te_long) < t->te_delta;
|
||||
}
|
||||
|
||||
static inline ManchesterEvent
|
||||
pp_manchester_event(uint32_t duration, bool level, const SubGhzBlockConst* t) {
|
||||
if(DURATION_DIFF(duration, t->te_short) < t->te_delta) {
|
||||
return level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
}
|
||||
if(DURATION_DIFF(duration, t->te_long) < t->te_delta) {
|
||||
return level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
return ManchesterEventReset;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
} ProtoPirateDecoderHeader;
|
||||
|
||||
uint8_t pp_decoder_hash_blocks(void* context);
|
||||
|
||||
void pp_decoder_free_default(void* context);
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
} ProtoPirateEncoderHeader;
|
||||
|
||||
void pp_encoder_free(void* context);
|
||||
void pp_encoder_stop(void* context);
|
||||
LevelDuration pp_encoder_yield(void* context);
|
||||
|
||||
#define PP_SHARED_UPLOAD_CAPACITY 2048U
|
||||
|
||||
void pp_encoder_buffer_ensure(void* context, size_t capacity);
|
||||
|
||||
LevelDuration* pp_shared_upload_buffer(void);
|
||||
size_t pp_shared_upload_capacity(void);
|
||||
void pp_shared_upload_release(void);
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/flipper_application/flipper_application.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include "protocol_items.h"
|
||||
|
||||
#define PROTOPIRATE_PROTOCOL_PLUGIN_APP_ID "protopirate_protocol_plugins"
|
||||
#define PROTOPIRATE_PROTOCOL_PLUGIN_API_VERSION 1U
|
||||
|
||||
typedef struct {
|
||||
const char* plugin_name;
|
||||
ProtoPirateProtocolRegistryFilter filter;
|
||||
const SubGhzProtocolRegistry* registry;
|
||||
} ProtoPirateProtocolPlugin;
|
||||
@@ -0,0 +1,42 @@
|
||||
#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 <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define PSA_PROTOCOL_NAME "PSA"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPSA SubGhzProtocolDecoderPSA;
|
||||
typedef struct SubGhzProtocolEncoderPSA SubGhzProtocolEncoderPSA;
|
||||
|
||||
extern const SubGhzProtocol psa_protocol;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_psa_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_psa_free(void* context);
|
||||
void subghz_protocol_decoder_psa_reset(void* context);
|
||||
void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_psa_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_psa_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_psa_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_psa_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions (not implemented yet)
|
||||
void* subghz_protocol_encoder_psa_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_psa_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_psa_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_psa_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_psa_yield(void* context);
|
||||
@@ -0,0 +1,27 @@
|
||||
#include "psa_bf_core.h"
|
||||
#include "protocols_common.h"
|
||||
|
||||
bool psa_bf_state_from_flipper_format(PsaBfState* state, FlipperFormat* ff) {
|
||||
furi_check(state);
|
||||
furi_check(ff);
|
||||
bool ok = false;
|
||||
do {
|
||||
uint64_t key1 = 0;
|
||||
if(!pp_flipper_read_hex_u64(ff, FF_KEY, &key1)) break;
|
||||
state->key1_low = (uint32_t)(key1 & 0xFFFFFFFF);
|
||||
state->key1_high = (uint32_t)((key1 >> 32) & 0xFFFFFFFF);
|
||||
|
||||
uint64_t key2 = 0;
|
||||
if(!pp_flipper_read_hex_u64(ff, "Key_2", &key2)) break;
|
||||
state->key2_low = (uint16_t)(key2 & 0xFFFF);
|
||||
|
||||
state->cancel = 0;
|
||||
state->progress_current = 0;
|
||||
state->progress_total = 0;
|
||||
state->status = PSA_BF_STATUS_IDLE;
|
||||
state->on_done = NULL;
|
||||
state->on_done_ctx = NULL;
|
||||
ok = true;
|
||||
} while(false);
|
||||
return ok;
|
||||
}
|
||||