mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-06-11 22:21:38 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 018a5feb29 | |||
| 4478f99dfe |
@@ -31,6 +31,7 @@ Protocols are split into **AM** and **FM** registries. The active registry is ch
|
||||
| Fiat V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code (static emu only) | ❌ | 315.00 / 433.92 |
|
||||
| Fiat V1 | ✅ | ❌ | Manchester | AM650 | Rolling Code | CRC8 | 315.00 / 433.92 |
|
||||
| Ford V0 | ✅ | ✅ | Manchester | AM650 | Rolling Code | ✅ + Checksum | 315.00 / 433.92 |
|
||||
| Honda V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Kia V1 | ✅ | ✅ | Manchester | AM650 | Rolling Code | CRC4 | 315.00 / 433.92 |
|
||||
| Porsche Touareg | ✅ | ❌ | PWM | AM650 | Rolling Code | ❌ | 315.00 / 433.92 |
|
||||
| PSA (Peugeot/Citroen) | ✅ | ✅ | Manchester | AM650 | XTEA/XOR | CRC8 | 315.00 / 433.92 |
|
||||
|
||||
@@ -6,7 +6,6 @@ App(
|
||||
entry_point="protopirate_app",
|
||||
requires=["gui"],
|
||||
stack_size=4 * 1024,
|
||||
sources=["*.c*"],
|
||||
fap_description="Decode car key fob signals from Sub-GHz",
|
||||
fap_version="3.0",
|
||||
fap_icon="images/protopirate_10px.png",
|
||||
@@ -28,7 +27,9 @@ App(
|
||||
"protocols/fiat_v0.c",
|
||||
"protocols/fiat_v1.c",
|
||||
"protocols/ford_v0.c",
|
||||
"protocols/honda_v1.c",
|
||||
"protocols/kia_v1.c",
|
||||
"protocols/kia_v2.c",
|
||||
"protocols/porsche_touareg.c",
|
||||
"protocols/psa.c",
|
||||
"protocols/psa_crypto.c",
|
||||
@@ -50,7 +51,6 @@ App(
|
||||
"protocols/keys.c",
|
||||
"protocols/scher_khan.c",
|
||||
"protocols/kia_v0.c",
|
||||
"protocols/kia_v2.c",
|
||||
"protocols/kia_v3_v4.c",
|
||||
"protocols/kia_v5.c",
|
||||
"protocols/kia_v6.c",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
//#define ENABLE_TIMING_TUNER_SCENE
|
||||
#define ENABLE_TIMING_TUNER_SCENE
|
||||
#define ENABLE_SUB_DECODE_SCENE
|
||||
|
||||
#define ENABLE_EMULATE_FEATURE
|
||||
|
||||
@@ -0,0 +1,697 @@
|
||||
#include "honda_v1.h"
|
||||
#include "protocols_common.h"
|
||||
#include <string.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"
|
||||
|
||||
_Static_assert(
|
||||
HONDA_V1_UPLOAD_CAPACITY <= PP_SHARED_UPLOAD_CAPACITY,
|
||||
"HONDA_V1_UPLOAD_CAPACITY exceeds shared upload slab");
|
||||
_Static_assert(
|
||||
HONDA_V1_PREAMBLE_UPLOAD_COUNT < HONDA_V1_UPLOAD_CAPACITY,
|
||||
"HONDA_V1 preamble exceeds upload slab");
|
||||
_Static_assert(
|
||||
HONDA_V1_FRAME_SYNC_DROP < HONDA_V1_FRAME_GENERATED_MAX,
|
||||
"HONDA_V1 frame sync drop exceeds generated frame");
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
struct SubGhzProtocolEncoderHondaV1 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t k2;
|
||||
};
|
||||
#endif
|
||||
|
||||
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 = pp_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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
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 = pp_emit_merge(
|
||||
generated,
|
||||
generated_count,
|
||||
COUNT_OF(generated),
|
||||
bit != 0U,
|
||||
HONDA_V1_TE_SHORT);
|
||||
generated_count = pp_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 = pp_emit(upload, *index, HONDA_V1_UPLOAD_CAPACITY, tail_level, HONDA_V1_TE_SHORT);
|
||||
if(!tail_level) {
|
||||
*index = pp_emit(upload, *index, HONDA_V1_UPLOAD_CAPACITY, true, HONDA_V1_TE_SHORT);
|
||||
}
|
||||
*index = pp_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 = pp_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);
|
||||
|
||||
pp_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;
|
||||
}
|
||||
#endif
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_v1_alloc,
|
||||
.free = pp_decoder_free_default,
|
||||
.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,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_v1_alloc,
|
||||
.free = pp_encoder_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_v1_deserialize,
|
||||
.stop = pp_encoder_stop,
|
||||
.yield = pp_encoder_yield,
|
||||
};
|
||||
#else
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
#endif
|
||||
|
||||
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
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
| SubGhzProtocolFlag_Send
|
||||
#endif
|
||||
,
|
||||
.decoder = &subghz_protocol_honda_v1_decoder,
|
||||
.encoder = &subghz_protocol_honda_v1_encoder,
|
||||
};
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
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;
|
||||
pp_encoder_buffer_ensure(instance, HONDA_V1_UPLOAD_CAPACITY);
|
||||
|
||||
return 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;
|
||||
|
||||
if(pp_verify_protocol_name(flipper_format, instance->base.protocol->name) !=
|
||||
SubGhzProtocolStatusOk) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, 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;
|
||||
pp_encoder_read_fields(flipper_format, &serial, &btn, &cnt, NULL);
|
||||
|
||||
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];
|
||||
pp_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
flipper_format_rewind(flipper_format);
|
||||
bool key_written = flipper_format_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data));
|
||||
if(!key_written) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
key_written =
|
||||
flipper_format_insert_or_update_hex(flipper_format, FF_KEY, key_data, sizeof(key_data));
|
||||
}
|
||||
if(!key_written) {
|
||||
return SubGhzProtocolStatusErrorParserKey;
|
||||
}
|
||||
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_SERIAL, instance->generic.serial);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_BTN, instance->generic.btn);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, FF_CNT, instance->generic.cnt);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V1_CRC_FIELD, instance->k2);
|
||||
pp_flipper_update_or_insert_u32(flipper_format, HONDA_V1_KEY_2_FIELD, instance->k2);
|
||||
|
||||
instance->encoder.repeat = pp_encoder_read_repeat(flipper_format, 1U);
|
||||
if(!honda_v1_build_upload(instance)) {
|
||||
return SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
#endif
|
||||
|
||||
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_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;
|
||||
}
|
||||
|
||||
status = pp_serialize_fields(
|
||||
flipper_format,
|
||||
PP_FIELD_SERIAL | PP_FIELD_BTN | PP_FIELD_CNT,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->generic.cnt,
|
||||
0);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return pp_write_display(
|
||||
flipper_format, instance->generic.protocol_name, honda_v1_button_name(instance->generic.btn));
|
||||
}
|
||||
|
||||
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 status;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
honda_v1_decode_fields(&instance->generic);
|
||||
|
||||
const uint8_t k2 = instance->k2 & HONDA_V1_NIBBLE_MASK;
|
||||
const bool crc_ok = honda_v1_crc_valid(instance->generic.data, k2);
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Btn:%s\r\n"
|
||||
"Sn:%07lX Cnt:%04lX\r\n"
|
||||
"Crc:%X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
(unsigned long long)instance->generic.data,
|
||||
honda_v1_button_name((uint8_t)instance->generic.btn),
|
||||
(unsigned long)instance->generic.serial,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
k2,
|
||||
crc_ok ? "OK" : "ERR");
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#include "../defines.h"
|
||||
|
||||
#define HONDA_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_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);
|
||||
|
||||
#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_honda_v1_alloc(SubGhzEnvironment* environment);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
#endif
|
||||
@@ -73,7 +73,7 @@ const SubGhzProtocolEncoder kia_protocol_v2_encoder = {
|
||||
const SubGhzProtocol kia_protocol_v2 = {
|
||||
.name = KIA_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &kia_protocol_v2_decoder,
|
||||
|
||||
@@ -4,18 +4,22 @@
|
||||
#include "../fiat_v1.h"
|
||||
#include "../ford_v0.h"
|
||||
#include "../kia_v1.h"
|
||||
#include "../kia_v2.h"
|
||||
#include "../porsche_touareg.h"
|
||||
#include "../psa.h"
|
||||
#include "../subaru.h"
|
||||
#include "../vag.h"
|
||||
#include "../star_line.h"
|
||||
#include "../honda_v1.h"
|
||||
|
||||
static const SubGhzProtocol* const protopirate_protocol_registry_am_items[] = {
|
||||
&chrysler_protocol_v0,
|
||||
&fiat_protocol_v0,
|
||||
&fiat_v1_protocol,
|
||||
&ford_protocol_v0,
|
||||
&honda_v1_protocol,
|
||||
&kia_protocol_v1,
|
||||
&kia_protocol_v2,
|
||||
&porsche_touareg_protocol,
|
||||
&psa_protocol,
|
||||
&subaru_protocol,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "../protopirate_protocol_plugins.h"
|
||||
#include "../scher_khan.h"
|
||||
#include "../kia_v0.h"
|
||||
#include "../kia_v2.h"
|
||||
#include "../kia_v3_v4.h"
|
||||
#include "../kia_v5.h"
|
||||
#include "../kia_v6.h"
|
||||
@@ -18,7 +17,6 @@
|
||||
static const SubGhzProtocol* const protopirate_protocol_registry_fm_items[] = {
|
||||
&subghz_protocol_scher_khan,
|
||||
&kia_protocol_v0,
|
||||
&kia_protocol_v2,
|
||||
&kia_protocol_v3_v4,
|
||||
&kia_protocol_v5,
|
||||
&kia_protocol_v6,
|
||||
|
||||
@@ -83,6 +83,14 @@ static const ProtoPirateProtocolTiming protocol_timings[] = {
|
||||
.te_delta = 120,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Honda V1: Manchester 1000/2000µs
|
||||
{
|
||||
.name = HONDA_V1_PROTOCOL_NAME,
|
||||
.te_short = 1000,
|
||||
.te_long = 2000,
|
||||
.te_delta = 400,
|
||||
.min_count_bit = 64,
|
||||
},
|
||||
// Kia V0: PWM 250/500µs — Kia 61bit, Suzuki 64bit, Honda V0 72bit
|
||||
{
|
||||
.name = "Kia V0",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "star_line.h"
|
||||
#include "psa.h"
|
||||
#include "honda_static.h"
|
||||
#include "honda_v1.h"
|
||||
|
||||
typedef enum {
|
||||
ProtoPirateProtocolRegistryFilterAM = 0,
|
||||
|
||||
@@ -473,6 +473,14 @@ static uint8_t emu_button_for_protocol(
|
||||
case InputKeyRight: return 0x3; // Un+Lk combo
|
||||
default: return original;
|
||||
}
|
||||
} else if(strstr(protocol, "Honda V1")) {
|
||||
switch(key) {
|
||||
case InputKeyUp: return 0x08; // Lock
|
||||
case InputKeyOk: return 0x00; // Unlock
|
||||
case InputKeyDown: return 0x09; // Trunk
|
||||
case InputKeyLeft: return 0x0A; // Panic
|
||||
default: return original;
|
||||
}
|
||||
} else if(strstr(protocol, "Honda Static")) {
|
||||
switch(key) {
|
||||
case InputKeyUp: return 0x1; // Lock
|
||||
|
||||
@@ -1,796 +0,0 @@
|
||||
#include "honda_static.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 512
|
||||
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||
|
||||
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;
|
||||
uint8_t _reserved_01[3];
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint8_t checksum;
|
||||
uint8_t _reserved_0d[3];
|
||||
} HondaStaticFields;
|
||||
|
||||
struct SubGhzProtocolDecoderHondaStatic {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
uint32_t _reserved_0c;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
uint32_t _reserved_20;
|
||||
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t packet_bit_count;
|
||||
uint8_t _reserved_5a;
|
||||
uint8_t _reserved_5b;
|
||||
|
||||
uint8_t symbols[HONDA_STATIC_SYMBOL_CAPACITY];
|
||||
uint16_t symbols_count;
|
||||
HondaStaticFields decoded;
|
||||
uint8_t decoded_valid;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHondaStatic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
HondaStaticFields decoded;
|
||||
uint8_t tx_button;
|
||||
uint8_t _reserved_69[3];
|
||||
};
|
||||
|
||||
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t value = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
value = (value << 8U) | bytes[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[7U - i] = (uint8_t)(value & 0xFFU);
|
||||
value >>= 8U;
|
||||
}
|
||||
}
|
||||
|
||||
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 uint8_t honda_static_sym_u8(uint8_t stored) {
|
||||
return stored ? 1U : 0U;
|
||||
}
|
||||
|
||||
static uint8_t honda_static_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;
|
||||
}
|
||||
|
||||
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* symbols,
|
||||
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_sym_u8(symbols[pos]);
|
||||
const uint8_t b = honda_static_sym_u8(symbols[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* symbols = instance->symbols;
|
||||
|
||||
uint16_t index = 1U;
|
||||
uint16_t transitions = 0U;
|
||||
|
||||
while(index < count) {
|
||||
if(honda_static_sym_u8(symbols[index]) != honda_static_sym_u8(symbols[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_sym_u8(symbols[index]) == honda_static_sym_u8(symbols[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(symbols, count, data_start, inverted, packet, &bit_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_forward_packet(packet, &instance->decoded)) {
|
||||
instance->decoded_valid = 1U;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(inverted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_reverse_packet(packet, &instance->decoded)) {
|
||||
instance->decoded_valid = 1U;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void honda_static_decoder_commit(SubGhzProtocolDecoderHondaStatic* instance) {
|
||||
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->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;
|
||||
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||
instance->encoder.upload[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;
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
if(!tmp) return false;
|
||||
bool ok = false;
|
||||
do {
|
||||
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
|
||||
|
||||
const char* key_str = furi_string_get_cstr(tmp);
|
||||
uint64_t key = 0;
|
||||
size_t hex_pos = 0;
|
||||
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
|
||||
char c = key_str[i];
|
||||
if(c == ' ') continue;
|
||||
uint8_t nibble;
|
||||
if(c >= '0' && c <= '9')
|
||||
nibble = c - '0';
|
||||
else if(c >= 'A' && c <= 'F')
|
||||
nibble = c - 'A' + 10;
|
||||
else if(c >= 'a' && c <= 'f')
|
||||
nibble = c - 'a' + 10;
|
||||
else
|
||||
break;
|
||||
key = (key << 4) | nibble;
|
||||
hex_pos++;
|
||||
}
|
||||
if(hex_pos != 16) break;
|
||||
*out_key = key;
|
||||
ok = true;
|
||||
} while(false);
|
||||
furi_string_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_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 subghz_protocol_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_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_honda_static_decoder,
|
||||
.encoder = &subghz_protocol_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.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;
|
||||
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
do {
|
||||
FuriString* pstr = furi_string_alloc();
|
||||
if(!pstr) break;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
furi_string_free(pstr);
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!honda_static_read_hex_u64(flipper_format, &key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
honda_static_unpack_compact(key, &instance->decoded);
|
||||
|
||||
uint32_t serial = instance->decoded.serial;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
instance->decoded.serial = serial;
|
||||
}
|
||||
|
||||
uint32_t btn_u32 = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
|
||||
uint8_t key_data[8];
|
||||
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
status = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 3U;
|
||||
}
|
||||
|
||||
honda_static_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
status = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0U;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
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;
|
||||
instance->decoded_valid = 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) {
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
}
|
||||
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) {
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
}
|
||||
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)) {
|
||||
honda_static_decoder_commit(instance);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
if(!instance->decoded_valid && (instance->generic.data != 0ULL)) {
|
||||
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||
instance->decoded_valid = 1U;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %ubit\r\n"
|
||||
"Key:%016llX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->packet_bit_count ? instance->packet_bit_count : HONDA_STATIC_BIT_COUNT,
|
||||
(unsigned long long)instance->generic.data);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"Btn:%s (0x%X)\r\n"
|
||||
"Ser:%07lX\r\n"
|
||||
"Cnt:%06lX Chk:%02X\r\n",
|
||||
honda_static_button_name(instance->decoded.button),
|
||||
instance->decoded.button,
|
||||
(unsigned long)instance->decoded.serial,
|
||||
(unsigned long)instance->decoded.counter,
|
||||
instance->decoded.checksum);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->decoded.serial, 1);
|
||||
|
||||
uint32_t temp = instance->decoded.button;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->decoded.counter, 1);
|
||||
|
||||
temp = instance->decoded.checksum;
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||
instance->decoded_valid = 1U;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t s = 0, b = 0, c = 0, k = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||
instance->decoded.serial = s;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||
instance->decoded.button = (uint8_t)b;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||
instance->decoded.counter = c & 0x00FFFFFFU;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Checksum", &k, 1)) {
|
||||
instance->decoded.checksum = (uint8_t)k;
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -1,811 +0,0 @@
|
||||
#include "honda_static.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;
|
||||
uint8_t _reserved_01[3];
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint8_t checksum;
|
||||
uint8_t _reserved_0d[3];
|
||||
} 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;
|
||||
uint8_t _reserved_69[3];
|
||||
};
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded);
|
||||
|
||||
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t value = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
value = (value << 8U) | bytes[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[7U - i] = (uint8_t)(value & 0xFFU);
|
||||
value >>= 8U;
|
||||
}
|
||||
}
|
||||
|
||||
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 uint8_t honda_static_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;
|
||||
}
|
||||
|
||||
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;
|
||||
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||
instance->encoder.upload[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;
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
if(!tmp) return false;
|
||||
bool ok = false;
|
||||
do {
|
||||
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
|
||||
|
||||
const char* key_str = furi_string_get_cstr(tmp);
|
||||
uint64_t key = 0;
|
||||
size_t hex_pos = 0;
|
||||
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
|
||||
char c = key_str[i];
|
||||
if(c == ' ') continue;
|
||||
uint8_t nibble;
|
||||
if(c >= '0' && c <= '9')
|
||||
nibble = c - '0';
|
||||
else if(c >= 'A' && c <= 'F')
|
||||
nibble = c - 'A' + 10;
|
||||
else if(c >= 'a' && c <= 'f')
|
||||
nibble = c - 'a' + 10;
|
||||
else
|
||||
break;
|
||||
key = (key << 4) | nibble;
|
||||
hex_pos++;
|
||||
}
|
||||
if(hex_pos != 16) break;
|
||||
*out_key = key;
|
||||
ok = true;
|
||||
} while(false);
|
||||
furi_string_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_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 subghz_protocol_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 = &subghz_protocol_honda_static_decoder,
|
||||
.encoder = &subghz_protocol_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.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;
|
||||
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
do {
|
||||
FuriString* pstr = furi_string_alloc();
|
||||
if(!pstr) break;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
furi_string_free(pstr);
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!honda_static_read_hex_u64(flipper_format, &key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
honda_static_unpack_compact(key, &instance->decoded);
|
||||
|
||||
uint32_t serial = instance->decoded.serial;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
instance->decoded.serial = serial;
|
||||
}
|
||||
|
||||
uint32_t btn_u32 = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
|
||||
uint8_t key_data[8];
|
||||
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
status = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 3U;
|
||||
}
|
||||
|
||||
honda_static_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
status = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0U;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t temp = decoded.serial;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.button;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.counter;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.checksum;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
uint32_t s = 0;
|
||||
uint32_t b = 0;
|
||||
uint32_t c = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||
decoded.serial = s;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||
decoded.button = (uint8_t)b;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||
decoded.counter = c & 0x00FFFFFFU;
|
||||
}
|
||||
|
||||
instance->generic.data = honda_static_pack_compact(&decoded);
|
||||
instance->generic.serial = decoded.serial;
|
||||
instance->generic.cnt = decoded.counter;
|
||||
instance->generic.btn = decoded.button;
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#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);
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#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);
|
||||
|
||||
@@ -85,6 +85,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&ford_protocol_v2,
|
||||
&ford_protocol_v3,
|
||||
&subghz_protocol_land_rover_v0,
|
||||
&subghz_protocol_toyota,
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -79,10 +79,12 @@
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
#include "chrysler.h"
|
||||
#include "honda_static.h"
|
||||
//#include "honda_static.h"
|
||||
//#include "honda_v1.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v1.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
#include "land_rover_v0.h"
|
||||
#include "toyota.h"
|
||||
|
||||
+607
-270
@@ -12,297 +12,50 @@
|
||||
static const SubGhzBlockConst subghz_protocol_subaru_const = {
|
||||
.te_short = 800,
|
||||
.te_long = 1600,
|
||||
.te_delta = 200,
|
||||
.te_delta = 250,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define SUBARU_PREAMBLE_PAIRS 75
|
||||
#define SUBARU_GAP_US 2800
|
||||
#define SUBARU_SYNC_US 2800
|
||||
#define SUBARU_UPLOAD_CAPACITY 400
|
||||
|
||||
/* ============================================================
|
||||
* STRUCT DEFINITIONS (NO typedef — matches subaru.h forward)
|
||||
* ============================================================ */
|
||||
|
||||
struct SubGhzProtocolDecoderSubaru {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
|
||||
uint16_t header_count;
|
||||
|
||||
uint16_t bit_count;
|
||||
uint8_t data[8];
|
||||
|
||||
uint64_t key;
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
uint8_t button;
|
||||
uint16_t count;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderSubaru {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
|
||||
uint64_t key;
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
uint8_t button;
|
||||
uint16_t count;
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
* HELPERS
|
||||
* ============================================================ */
|
||||
typedef enum {
|
||||
SubaruDecoderStepReset = 0,
|
||||
SubaruDecoderStepCheckPreamble,
|
||||
SubaruDecoderStepFoundGap,
|
||||
SubaruDecoderStepFoundSync,
|
||||
SubaruDecoderStepSaveDuration,
|
||||
SubaruDecoderStepCheckDuration,
|
||||
} SubaruDecoderStep;
|
||||
|
||||
static const char* subaru_get_button_name(uint8_t btn) {
|
||||
switch(btn & 0x03) {
|
||||
case 0: return "Lock";
|
||||
case 1: return "Unlock";
|
||||
case 2: return "Trunk";
|
||||
case 3: return "Panic";
|
||||
default: return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static void subaru_rotate_left_3(uint8_t* a, uint8_t* b, uint8_t* c, uint8_t count) {
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
uint8_t t = *a;
|
||||
*a = (*a << 1) | (*b >> 7);
|
||||
*b = (*b << 1) | (*c >> 7);
|
||||
*c = (*c << 1) | (t >> 7);
|
||||
}
|
||||
}
|
||||
|
||||
static void subaru_decode_fields(
|
||||
const uint8_t* kb,
|
||||
uint32_t* serial,
|
||||
uint8_t* btn,
|
||||
uint16_t* cnt) {
|
||||
|
||||
*btn = kb[0] & 0x0F;
|
||||
*serial = ((uint32_t)kb[1] << 16) |
|
||||
((uint32_t)kb[2] << 8) |
|
||||
kb[3];
|
||||
|
||||
uint8_t lo = 0;
|
||||
|
||||
if(!(kb[4] & 0x40)) lo |= 0x01;
|
||||
if(!(kb[4] & 0x80)) lo |= 0x02;
|
||||
if(!(kb[5] & 0x01)) lo |= 0x04;
|
||||
if(!(kb[5] & 0x02)) lo |= 0x08;
|
||||
if(!(kb[6] & 0x01)) lo |= 0x10;
|
||||
if(!(kb[6] & 0x02)) lo |= 0x20;
|
||||
if(!(kb[5] & 0x40)) lo |= 0x40;
|
||||
if(!(kb[5] & 0x80)) lo |= 0x80;
|
||||
|
||||
uint8_t reg1 =
|
||||
((kb[7] & 0x0F) << 4) |
|
||||
(kb[5] & 0x0C) |
|
||||
((kb[6] >> 6) & 0x03);
|
||||
|
||||
uint8_t reg2 =
|
||||
((kb[6] & 0x3C) << 2) |
|
||||
((kb[7] >> 4) & 0x0F);
|
||||
|
||||
uint8_t s0 = kb[3];
|
||||
uint8_t s1 = kb[1];
|
||||
uint8_t s2 = kb[2];
|
||||
|
||||
subaru_rotate_left_3(&s0, &s1, &s2, 4 + lo);
|
||||
|
||||
uint8_t t1 = s1 ^ reg1;
|
||||
uint8_t t2 = s2 ^ reg2;
|
||||
|
||||
uint8_t hi = 0;
|
||||
|
||||
if(!(t1 & 0x10)) hi |= 0x04;
|
||||
if(!(t1 & 0x20)) hi |= 0x08;
|
||||
if(!(t2 & 0x80)) hi |= 0x02;
|
||||
if(!(t2 & 0x40)) hi |= 0x01;
|
||||
if(!(t1 & 0x01)) hi |= 0x40;
|
||||
if(!(t1 & 0x02)) hi |= 0x80;
|
||||
if(!(t2 & 0x08)) hi |= 0x20;
|
||||
if(!(t2 & 0x04)) hi |= 0x10;
|
||||
|
||||
*cnt = ((uint16_t)hi << 8) | lo;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* PROTOCOL TABLE
|
||||
* ============================================================ */
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_subaru_decoder;
|
||||
const SubGhzProtocolEncoder subghz_protocol_subaru_encoder;
|
||||
|
||||
const SubGhzProtocol subghz_protocol_subaru = {
|
||||
.name = SUBGHZ_PROTOCOL_SUBARU_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_subaru_decoder,
|
||||
.encoder = &subghz_protocol_subaru_encoder,
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
* DECODER IMPLEMENTATION
|
||||
* ============================================================ */
|
||||
|
||||
void* subghz_protocol_decoder_subaru_alloc(SubGhzEnvironment* env) {
|
||||
UNUSED(env);
|
||||
SubGhzProtocolDecoderSubaru* i = malloc(sizeof(SubGhzProtocolDecoderSubaru));
|
||||
i->base.protocol = &subghz_protocol_subaru;
|
||||
i->generic.protocol_name = i->base.protocol->name;
|
||||
return i;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_free(void* ctx) {
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_reset(void* ctx) {
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
i->decoder.parser_step = 0;
|
||||
i->decoder.decode_data = 0;
|
||||
i->decoder.decode_count_bit = 0;
|
||||
i->header_count = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_feed(void* ctx, bool level, uint32_t dur) {
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
|
||||
const uint32_t te_short = subghz_protocol_subaru_const.te_short;
|
||||
const uint32_t te_long = subghz_protocol_subaru_const.te_long;
|
||||
const uint32_t delta = subghz_protocol_subaru_const.te_delta;
|
||||
|
||||
switch(i->decoder.parser_step) {
|
||||
|
||||
case 0:
|
||||
if(level && DURATION_DIFF(dur, te_long) < delta) {
|
||||
i->decoder.decode_data = 0;
|
||||
i->decoder.decode_count_bit = 0;
|
||||
i->header_count = 0;
|
||||
i->decoder.parser_step = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if(!level && DURATION_DIFF(dur, te_long) < delta) {
|
||||
i->header_count++;
|
||||
} else if(!level && DURATION_DIFF(dur, SUBARU_GAP_US) < 800) {
|
||||
if(i->header_count > 20)
|
||||
i->decoder.parser_step = 2;
|
||||
else
|
||||
i->decoder.parser_step = 0;
|
||||
} else {
|
||||
i->decoder.parser_step = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if(level && DURATION_DIFF(dur, SUBARU_SYNC_US) < 800) {
|
||||
i->decoder.parser_step = 3;
|
||||
} else {
|
||||
i->decoder.parser_step = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(!level) {
|
||||
i->decoder.te_last = dur;
|
||||
i->decoder.parser_step = 4;
|
||||
} else {
|
||||
i->decoder.parser_step = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if(level) {
|
||||
bool bit;
|
||||
if(DURATION_DIFF(dur, te_long) < delta &&
|
||||
DURATION_DIFF(i->decoder.te_last, te_short) < delta) {
|
||||
bit = false;
|
||||
} else if(DURATION_DIFF(dur, te_short) < delta &&
|
||||
DURATION_DIFF(i->decoder.te_last, te_long) < delta) {
|
||||
bit = true;
|
||||
} else {
|
||||
i->decoder.parser_step = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
subghz_protocol_blocks_add_bit(&i->decoder, bit);
|
||||
|
||||
if(i->decoder.decode_count_bit >= 64) {
|
||||
|
||||
i->generic.data = i->decoder.decode_data;
|
||||
i->generic.data_count_bit = 64;
|
||||
|
||||
uint8_t b[8];
|
||||
for(int k = 0; k < 8; k++)
|
||||
b[k] = (i->generic.data >> (56 - 8*k)) & 0xFF;
|
||||
|
||||
subaru_decode_fields(b, &i->serial, &i->btn, &i->cnt);
|
||||
|
||||
i->generic.serial = i->serial;
|
||||
i->generic.btn = i->btn;
|
||||
i->generic.cnt = i->cnt;
|
||||
|
||||
if(i->base.callback)
|
||||
i->base.callback(&i->base, i->base.context);
|
||||
|
||||
i->decoder.parser_step = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_subaru_get_hash_data(void* ctx) {
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&i->decoder,
|
||||
(i->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_subaru_serialize(
|
||||
void* ctx,
|
||||
FlipperFormat* ff,
|
||||
SubGhzRadioPreset* preset) {
|
||||
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
return subghz_block_generic_serialize(&i->generic, ff, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_subaru_deserialize(
|
||||
void* ctx,
|
||||
FlipperFormat* ff) {
|
||||
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&i->generic,
|
||||
ff,
|
||||
subghz_protocol_subaru_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_get_string(void* ctx, FuriString* out) {
|
||||
SubGhzProtocolDecoderSubaru* i = ctx;
|
||||
|
||||
furi_string_cat_printf(
|
||||
out,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%06lX Btn:%01X [%s]\r\n"
|
||||
"Cnt:%04X",
|
||||
i->generic.protocol_name,
|
||||
i->generic.data_count_bit,
|
||||
i->generic.data,
|
||||
i->serial,
|
||||
i->btn,
|
||||
subaru_get_button_name(i->btn),
|
||||
i->cnt);
|
||||
}
|
||||
static void subaru_decode_count(const uint8_t* KB, uint16_t* count);
|
||||
static void subaru_encode_count(uint8_t* KB, uint16_t count);
|
||||
static void subaru_add_bit(SubGhzProtocolDecoderSubaru* instance, bool bit);
|
||||
static bool subaru_process_data(SubGhzProtocolDecoderSubaru* instance);
|
||||
static void subghz_protocol_encoder_subaru_get_upload(SubGhzProtocolEncoderSubaru* instance);
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_subaru_decoder = {
|
||||
.alloc = subghz_protocol_decoder_subaru_alloc,
|
||||
@@ -314,3 +67,587 @@ const SubGhzProtocolDecoder subghz_protocol_subaru_decoder = {
|
||||
.deserialize = subghz_protocol_decoder_subaru_deserialize,
|
||||
.get_string = subghz_protocol_decoder_subaru_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_subaru_encoder = {
|
||||
.alloc = subghz_protocol_encoder_subaru_alloc,
|
||||
.free = subghz_protocol_encoder_subaru_free,
|
||||
.deserialize = subghz_protocol_encoder_subaru_deserialize,
|
||||
.stop = subghz_protocol_encoder_subaru_stop,
|
||||
.yield = subghz_protocol_encoder_subaru_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_subaru = {
|
||||
.name = SUBGHZ_PROTOCOL_SUBARU_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_subaru_decoder,
|
||||
.encoder = &subghz_protocol_subaru_encoder,
|
||||
};
|
||||
|
||||
static uint8_t subaru_get_button_code(uint8_t custom_btn) {
|
||||
switch(custom_btn) {
|
||||
case 1: return 0x01;
|
||||
case 2: return 0x02;
|
||||
case 3: return 0x03;
|
||||
case 4: return 0x04;
|
||||
case 5: return 0x08;
|
||||
default: return 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t subaru_btn_to_custom(uint8_t btn_code) {
|
||||
switch(btn_code) {
|
||||
case 0x01: return 1;
|
||||
case 0x02: return 2;
|
||||
case 0x03: return 3;
|
||||
case 0x04: return 4;
|
||||
case 0x08: return 5;
|
||||
default: return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* subaru_get_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01: return "Lock";
|
||||
case 0x02: return "Unlock";
|
||||
case 0x03: return "Trunk";
|
||||
case 0x04: return "Panic";
|
||||
case 0x08: return "0x08";
|
||||
default: return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static void subaru_decode_count(const uint8_t* KB, uint16_t* count) {
|
||||
uint8_t lo = 0;
|
||||
if((KB[4] & 0x40) == 0) lo |= 0x01;
|
||||
if((KB[4] & 0x80) == 0) lo |= 0x02;
|
||||
if((KB[5] & 0x01) == 0) lo |= 0x04;
|
||||
if((KB[5] & 0x02) == 0) lo |= 0x08;
|
||||
if((KB[6] & 0x01) == 0) lo |= 0x10;
|
||||
if((KB[6] & 0x02) == 0) lo |= 0x20;
|
||||
if((KB[5] & 0x40) == 0) lo |= 0x40;
|
||||
if((KB[5] & 0x80) == 0) lo |= 0x80;
|
||||
|
||||
uint8_t REG_SH1 = (KB[7] << 4) & 0xF0;
|
||||
if(KB[5] & 0x04) REG_SH1 |= 0x04;
|
||||
if(KB[5] & 0x08) REG_SH1 |= 0x08;
|
||||
if(KB[6] & 0x80) REG_SH1 |= 0x02;
|
||||
if(KB[6] & 0x40) REG_SH1 |= 0x01;
|
||||
|
||||
uint8_t REG_SH2 = ((KB[6] << 2) & 0xF0) | ((KB[7] >> 4) & 0x0F);
|
||||
|
||||
uint8_t SER0 = KB[3];
|
||||
uint8_t SER1 = KB[1];
|
||||
uint8_t SER2 = KB[2];
|
||||
|
||||
uint8_t total_rot = 4 + lo;
|
||||
for(uint8_t i = 0; i < total_rot; ++i) {
|
||||
uint8_t t_bit = (SER0 >> 7) & 1;
|
||||
SER0 = ((SER0 << 1) & 0xFE) | ((SER1 >> 7) & 1);
|
||||
SER1 = ((SER1 << 1) & 0xFE) | ((SER2 >> 7) & 1);
|
||||
SER2 = ((SER2 << 1) & 0xFE) | t_bit;
|
||||
}
|
||||
|
||||
uint8_t T1 = SER1 ^ REG_SH1;
|
||||
uint8_t T2 = SER2 ^ REG_SH2;
|
||||
|
||||
uint8_t hi = 0;
|
||||
if((T1 & 0x10) == 0) hi |= 0x04;
|
||||
if((T1 & 0x20) == 0) hi |= 0x08;
|
||||
if((T2 & 0x80) == 0) hi |= 0x02;
|
||||
if((T2 & 0x40) == 0) hi |= 0x01;
|
||||
if((T1 & 0x01) == 0) hi |= 0x40;
|
||||
if((T1 & 0x02) == 0) hi |= 0x80;
|
||||
if((T2 & 0x08) == 0) hi |= 0x20;
|
||||
if((T2 & 0x04) == 0) hi |= 0x10;
|
||||
|
||||
*count = ((hi << 8) | lo) & 0xFFFF;
|
||||
}
|
||||
|
||||
static void subaru_encode_count(uint8_t* KB, uint16_t count) {
|
||||
uint8_t lo = count & 0xFF;
|
||||
uint8_t hi = (count >> 8) & 0xFF;
|
||||
|
||||
KB[4] &= ~0xC0;
|
||||
KB[5] &= ~0xC3;
|
||||
KB[6] &= ~0x03;
|
||||
|
||||
if((lo & 0x01) == 0) KB[4] |= 0x40;
|
||||
if((lo & 0x02) == 0) KB[4] |= 0x80;
|
||||
if((lo & 0x04) == 0) KB[5] |= 0x01;
|
||||
if((lo & 0x08) == 0) KB[5] |= 0x02;
|
||||
if((lo & 0x10) == 0) KB[6] |= 0x01;
|
||||
if((lo & 0x20) == 0) KB[6] |= 0x02;
|
||||
if((lo & 0x40) == 0) KB[5] |= 0x40;
|
||||
if((lo & 0x80) == 0) KB[5] |= 0x80;
|
||||
|
||||
uint8_t SER0 = KB[3];
|
||||
uint8_t SER1 = KB[1];
|
||||
uint8_t SER2 = KB[2];
|
||||
|
||||
uint8_t total_rot = 4 + lo;
|
||||
for(uint8_t i = 0; i < total_rot; ++i) {
|
||||
uint8_t t_bit = (SER0 >> 7) & 1;
|
||||
SER0 = ((SER0 << 1) & 0xFE) | ((SER1 >> 7) & 1);
|
||||
SER1 = ((SER1 << 1) & 0xFE) | ((SER2 >> 7) & 1);
|
||||
SER2 = ((SER2 << 1) & 0xFE) | t_bit;
|
||||
}
|
||||
|
||||
uint8_t T1 = 0xFF;
|
||||
uint8_t T2 = 0xFF;
|
||||
|
||||
if(hi & 0x04) T1 &= ~0x10;
|
||||
if(hi & 0x08) T1 &= ~0x20;
|
||||
if(hi & 0x02) T2 &= ~0x80;
|
||||
if(hi & 0x01) T2 &= ~0x40;
|
||||
if(hi & 0x40) T1 &= ~0x01;
|
||||
if(hi & 0x80) T1 &= ~0x02;
|
||||
if(hi & 0x20) T2 &= ~0x08;
|
||||
if(hi & 0x10) T2 &= ~0x04;
|
||||
|
||||
uint8_t new_REG_SH1 = T1 ^ SER1;
|
||||
uint8_t new_REG_SH2 = T2 ^ SER2;
|
||||
|
||||
KB[5] &= ~0x0C;
|
||||
KB[6] &= ~0xC0;
|
||||
|
||||
KB[7] = (KB[7] & 0xF0) | ((new_REG_SH1 >> 4) & 0x0F);
|
||||
|
||||
if(new_REG_SH1 & 0x04) KB[5] |= 0x04;
|
||||
if(new_REG_SH1 & 0x08) KB[5] |= 0x08;
|
||||
if(new_REG_SH1 & 0x02) KB[6] |= 0x80;
|
||||
if(new_REG_SH1 & 0x01) KB[6] |= 0x40;
|
||||
|
||||
KB[6] = (KB[6] & 0xC3) | ((new_REG_SH2 >> 2) & 0x3C);
|
||||
|
||||
KB[7] = (KB[7] & 0x0F) | ((new_REG_SH2 << 4) & 0xF0);
|
||||
}
|
||||
|
||||
static void subaru_add_bit(SubGhzProtocolDecoderSubaru* instance, bool bit) {
|
||||
if(instance->bit_count < 64) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_idx = 7 - (instance->bit_count % 8);
|
||||
if(bit) {
|
||||
instance->data[byte_idx] |= (1 << bit_idx);
|
||||
} else {
|
||||
instance->data[byte_idx] &= ~(1 << bit_idx);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static bool subaru_process_data(SubGhzProtocolDecoderSubaru* instance) {
|
||||
if(instance->bit_count < 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* b = instance->data;
|
||||
|
||||
instance->key = ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) |
|
||||
((uint64_t)b[2] << 40) | ((uint64_t)b[3] << 32) |
|
||||
((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
|
||||
((uint64_t)b[6] << 8) | ((uint64_t)b[7]);
|
||||
|
||||
instance->serial = ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3];
|
||||
instance->button = b[0] & 0x0F;
|
||||
subaru_decode_count(b, &instance->count);
|
||||
|
||||
instance->decoder.decode_data = instance->key;
|
||||
instance->decoder.decode_count_bit = 64;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_subaru_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderSubaru* instance = malloc(sizeof(SubGhzProtocolDecoderSubaru));
|
||||
instance->base.protocol = &subghz_protocol_subaru;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->header_count = 0;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->data, 0, sizeof(instance->data));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case SubaruDecoderStepReset:
|
||||
if(level && DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
instance->decoder.parser_step = SubaruDecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case SubaruDecoderStepCheckPreamble:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
instance->header_count++;
|
||||
} else if(duration > 2000 && duration < 3500) {
|
||||
if(instance->header_count > 20) {
|
||||
instance->decoder.parser_step = SubaruDecoderStepFoundGap;
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SubaruDecoderStepFoundGap:
|
||||
if(level && duration > 2000 && duration < 3500) {
|
||||
instance->decoder.parser_step = SubaruDecoderStepFoundSync;
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case SubaruDecoderStepFoundSync:
|
||||
if(!level && DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
instance->decoder.parser_step = SubaruDecoderStepSaveDuration;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->data, 0, sizeof(instance->data));
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case SubaruDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_subaru_const.te_short) < subghz_protocol_subaru_const.te_delta) {
|
||||
subaru_add_bit(instance, true);
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = SubaruDecoderStepCheckDuration;
|
||||
} else if(DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
subaru_add_bit(instance, false);
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = SubaruDecoderStepCheckDuration;
|
||||
} else if(duration > 3000) {
|
||||
if(instance->bit_count >= 64) {
|
||||
if(subaru_process_data(instance)) {
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.data_count_bit = 64;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
uint8_t custom_btn = subaru_btn_to_custom(instance->button);
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(custom_btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case SubaruDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_subaru_const.te_short) < subghz_protocol_subaru_const.te_delta ||
|
||||
DURATION_DIFF(duration, subghz_protocol_subaru_const.te_long) < subghz_protocol_subaru_const.te_delta) {
|
||||
instance->decoder.parser_step = SubaruDecoderStepSaveDuration;
|
||||
} else if(duration > 3000) {
|
||||
if(instance->bit_count >= 64) {
|
||||
if(subaru_process_data(instance)) {
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.data_count_bit = 64;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
uint8_t custom_btn = subaru_btn_to_custom(instance->button);
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(custom_btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = SubaruDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_subaru_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_subaru_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_subaru_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_subaru_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
instance->key = instance->generic.data;
|
||||
|
||||
uint8_t b[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
b[i] = (uint8_t)(instance->key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
instance->serial = ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3];
|
||||
instance->button = b[0] & 0x0F;
|
||||
subaru_decode_count(b, &instance->count);
|
||||
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
uint8_t custom_btn = subaru_btn_to_custom(instance->button);
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(custom_btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_subaru_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderSubaru* instance = context;
|
||||
|
||||
uint32_t key_hi = (uint32_t)(instance->key >> 32);
|
||||
uint32_t key_lo = (uint32_t)(instance->key & 0xFFFFFFFF);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%06lX Cnt:%04X\r\n"
|
||||
"Btn:%X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
instance->serial,
|
||||
instance->count,
|
||||
instance->button,
|
||||
subaru_get_button_name(instance->button));
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_subaru_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderSubaru* instance = malloc(sizeof(SubGhzProtocolEncoderSubaru));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_subaru;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 2048;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_subaru_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderSubaru* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_subaru_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderSubaru* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_subaru_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderSubaru* 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) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_subaru_get_upload(SubGhzProtocolEncoderSubaru* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
uint32_t te_short = subghz_protocol_subaru_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_subaru_const.te_long;
|
||||
uint32_t gap_duration = 2500;
|
||||
uint32_t sync_duration = 2500;
|
||||
|
||||
for(int i = 0; i < 20; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_long);
|
||||
}
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, gap_duration);
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_long);
|
||||
|
||||
for(int i = 63; i >= 0; i--) {
|
||||
bool bit = (instance->key >> i) & 1;
|
||||
if(bit) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, gap_duration * 2);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_subaru_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderSubaru* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t original_key = instance->generic.data;
|
||||
|
||||
uint8_t b[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
b[i] = (uint8_t)(original_key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
instance->serial = ((uint32_t)b[1] << 16) | ((uint32_t)b[2] << 8) | b[3];
|
||||
instance->button = b[0] & 0x0F;
|
||||
subaru_decode_count(b, &instance->count);
|
||||
|
||||
uint8_t original_custom_btn = subaru_btn_to_custom(instance->button);
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(original_custom_btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
uint8_t selected_custom_btn;
|
||||
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
|
||||
selected_custom_btn = subghz_custom_btn_get_original();
|
||||
} else {
|
||||
selected_custom_btn = subghz_custom_btn_get();
|
||||
}
|
||||
|
||||
uint8_t new_button = subaru_get_button_code(selected_custom_btn);
|
||||
instance->button = new_button;
|
||||
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->count = (instance->count + mult) & 0xFFFF;
|
||||
|
||||
b[0] = (b[0] & 0xF0) | (instance->button & 0x0F);
|
||||
|
||||
subaru_encode_count(b, instance->count);
|
||||
|
||||
instance->key = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->key = (instance->key << 8) | b[i];
|
||||
}
|
||||
|
||||
instance->generic.data = instance->key;
|
||||
instance->generic.serial = instance->serial;
|
||||
instance->generic.btn = instance->button;
|
||||
instance->generic.cnt = instance->count;
|
||||
|
||||
subghz_protocol_encoder_subaru_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->key >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,642 @@
|
||||
#include "toyota.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolToyota"
|
||||
|
||||
/*
|
||||
* TOYOTA KEELOQ — DUAL VARIANT DECODER
|
||||
*
|
||||
* VARIANT A — Corolla Verso 2004-2010 (433 MHz)
|
||||
* TE short : 400 us TE long : 800 us delta: 175 us
|
||||
* Encoding : PWM pairs — LS=0 (Long HIGH + Short LOW)
|
||||
* SL=1 (Short HIGH + Long LOW)
|
||||
* Preamble : repeated SS pairs, ends on first non-SS pair
|
||||
* Frame : 68 bits
|
||||
* Repeats : 8x
|
||||
*
|
||||
* VARIANT B — Tundra 2011 (315 MHz)
|
||||
* Encoding : NRZ — each individual pulse encodes one bit:
|
||||
* pulse <= 287 us -> bit 0
|
||||
* pulse > 287 us -> bit 1
|
||||
* Preamble : alternating short/long pulses (~200/~390 us each)
|
||||
* ends with sync gap ~1938 us (a LOW > 1500 us)
|
||||
* Frame : 67 bits (each pulse = 1 bit, HIGH and LOW alike)
|
||||
* Repeats : 30x
|
||||
* Inter-frame gap: ~51000 us
|
||||
*
|
||||
* Confirmed from real capture analysis:
|
||||
* Preamble HIGHs: ~200 us (short)
|
||||
* Preamble LOWs : ~390 us (long) — counted as preamble pairs
|
||||
* Sync gap : ~1938 us LOW after last preamble HIGH
|
||||
* Data pulses : each pulse independently = 0 if <=287us, 1 if >287us
|
||||
* Boundary 287 = midpoint between 200 us and 375 us centers
|
||||
*
|
||||
* generic.data (64 bits):
|
||||
* [63..32] = hop (32 bits)
|
||||
* [31..4] = serial (28 bits)
|
||||
* [3..0] = button (4 bits)
|
||||
*
|
||||
* generic.cnt:
|
||||
* 0 = Variant A (Corolla/433MHz)
|
||||
* 1 = Variant B (Tundra/315MHz)
|
||||
*/
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Physical constants — Variant A
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static const SubGhzBlockConst toyota_const_a = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 175,
|
||||
.min_count_bit_for_found = 60,
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Physical constants — Variant B (preamble classification only)
|
||||
* Data phase uses midpoint, not these tolerances.
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static const SubGhzBlockConst toyota_const_b = {
|
||||
.te_short = 200,
|
||||
.te_long = 390,
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 60,
|
||||
};
|
||||
|
||||
/*
|
||||
* NRZ midpoint for Variant B data pulses.
|
||||
* Pulses <= this value are bit 0, pulses > this value are bit 1.
|
||||
* Midpoint between short center (200us) and long center (375us).
|
||||
* Value 287 confirmed correct against real capture:
|
||||
* 192us->0 181us->0 383us->1 434us->1 380us->1 etc.
|
||||
*/
|
||||
#define TOYOTA_B_NRZ_MIDPOINT 287u
|
||||
|
||||
/* Sync gap: LOW pulse separating preamble from data */
|
||||
#define TOYOTA_B_SYNC_GAP_MIN 1500u
|
||||
#define TOYOTA_B_SYNC_GAP_MAX 2600u
|
||||
|
||||
/* Any pulse above this is an inter-frame gap */
|
||||
#define TOYOTA_INTER_FRAME_GAP 5000u
|
||||
|
||||
/* Minimum preamble pairs before accepting sync gap */
|
||||
#define TOYOTA_B_PREAMBLE_MIN 6u
|
||||
#define TOYOTA_A_PREAMBLE_MIN 6u
|
||||
|
||||
/* Frame lengths in bits */
|
||||
#define TOYOTA_A_BITS 68u
|
||||
#define TOYOTA_B_BITS 67u
|
||||
|
||||
/* First HIGH duration below this -> Variant B, above -> Variant A */
|
||||
#define TOYOTA_VARIANT_THRESH 310u
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Button codes
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
#define TOYOTA_A_BTN_LOCK 0x08
|
||||
#define TOYOTA_A_BTN_UNLOCK 0x01
|
||||
|
||||
#define TOYOTA_B_BTN_LOCK 0x0A
|
||||
#define TOYOTA_B_BTN_UNLOCK 0x05
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Parser states
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
typedef enum {
|
||||
ToyotaStepReset = 0,
|
||||
ToyotaStepPreambleA,
|
||||
ToyotaStepDataA,
|
||||
ToyotaStepPreambleB,
|
||||
ToyotaStepDataB,
|
||||
} ToyotaDecoderStep;
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Decoder instance
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t bits_lo;
|
||||
uint8_t bits_hi;
|
||||
uint8_t bit_count;
|
||||
|
||||
uint32_t te_last;
|
||||
bool have_high;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t variant; /* 0 = Corolla/433MHz, 1 = Tundra/315MHz */
|
||||
|
||||
uint32_t hop;
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
|
||||
} SubGhzProtocolDecoderToyota;
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Protocol descriptor
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_toyota_decoder = {
|
||||
.alloc = subghz_protocol_decoder_toyota_alloc,
|
||||
.free = subghz_protocol_decoder_toyota_free,
|
||||
.feed = subghz_protocol_decoder_toyota_feed,
|
||||
.reset = subghz_protocol_decoder_toyota_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_toyota_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_toyota_serialize,
|
||||
.deserialize = subghz_protocol_decoder_toyota_deserialize,
|
||||
.get_string = subghz_protocol_decoder_toyota_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_toyota = {
|
||||
.name = SUBGHZ_PROTOCOL_TOYOTA_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_315 |
|
||||
SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_toyota_decoder,
|
||||
.encoder = NULL,
|
||||
};
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* TE helpers (preamble phase only)
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static inline bool te_is_short(uint32_t d, const SubGhzBlockConst* c) {
|
||||
return DURATION_DIFF(d, (uint32_t)c->te_short) < (uint32_t)c->te_delta;
|
||||
}
|
||||
|
||||
static inline bool te_is_long(uint32_t d, const SubGhzBlockConst* c) {
|
||||
return DURATION_DIFF(d, (uint32_t)c->te_long) < (uint32_t)c->te_delta;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Bit accumulator
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static void toyota_push_bit(SubGhzProtocolDecoderToyota* inst, uint8_t bit) {
|
||||
uint8_t carry = (uint8_t)(inst->bits_lo >> 63) & 1;
|
||||
inst->bits_hi = (inst->bits_hi << 1) | carry;
|
||||
inst->bits_lo = (inst->bits_lo << 1) | (bit & 1);
|
||||
inst->bit_count++;
|
||||
}
|
||||
|
||||
static uint32_t toyota_extract(
|
||||
const SubGhzProtocolDecoderToyota* inst,
|
||||
uint8_t offset,
|
||||
uint8_t length)
|
||||
{
|
||||
uint32_t result = 0;
|
||||
uint8_t total = inst->bit_count;
|
||||
for(uint8_t i = 0; i < length; i++) {
|
||||
int8_t pos = (int8_t)(total - 1) - (int8_t)(offset + i);
|
||||
uint8_t b = 0;
|
||||
if(pos >= 64) b = (inst->bits_hi >> (pos - 64)) & 1;
|
||||
else if(pos >= 0) b = (inst->bits_lo >> pos) & 1;
|
||||
result = (result << 1) | b;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Name helpers
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static const char* toyota_button_name(uint8_t btn, uint8_t variant) {
|
||||
if(variant == 1) {
|
||||
switch(btn & 0x0F) {
|
||||
case TOYOTA_B_BTN_LOCK: return "Lock";
|
||||
case TOYOTA_B_BTN_UNLOCK: return "Unlock";
|
||||
case 0x0F: return "Lock+Unlock";
|
||||
case 0x04: return "Trunk";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
switch(btn & 0x0F) {
|
||||
case TOYOTA_A_BTN_LOCK: return "Lock";
|
||||
case TOYOTA_A_BTN_UNLOCK: return "Unlock";
|
||||
case 0x09: return "Lock+Unlock";
|
||||
case 0x02: return "Trunk";
|
||||
case 0x04: return "Aux";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static const char* toyota_model_name(uint8_t variant) {
|
||||
return (variant == 1) ? "Tundra" : "Corolla";
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Decode and fire callback
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static void toyota_decode_and_fire(SubGhzProtocolDecoderToyota* inst) {
|
||||
const SubGhzBlockConst* c =
|
||||
(inst->variant == 1) ? &toyota_const_b : &toyota_const_a;
|
||||
|
||||
if(inst->bit_count < (uint8_t)c->min_count_bit_for_found) return;
|
||||
|
||||
inst->hop = toyota_extract(inst, 0, 32);
|
||||
inst->serial = toyota_extract(inst, 32, 28);
|
||||
inst->button = (uint8_t)toyota_extract(inst, 60, 4);
|
||||
|
||||
inst->generic.data =
|
||||
((uint64_t)inst->hop << 32) |
|
||||
((uint64_t)inst->serial << 4) |
|
||||
((uint64_t)inst->button & 0x0F);
|
||||
|
||||
inst->generic.data_count_bit = inst->bit_count;
|
||||
inst->generic.serial = inst->serial;
|
||||
inst->generic.btn = inst->button;
|
||||
inst->generic.cnt = inst->variant;
|
||||
|
||||
inst->decoder.decode_data = inst->generic.data;
|
||||
inst->decoder.decode_count_bit = inst->generic.data_count_bit;
|
||||
|
||||
FURI_LOG_D(TAG, "FIRE var=%d bits=%d hop=%08lX serial=%07lX btn=%X",
|
||||
(int)inst->variant, (int)inst->bit_count,
|
||||
(unsigned long)inst->hop,
|
||||
(unsigned long)inst->serial,
|
||||
(unsigned int)inst->button);
|
||||
|
||||
if(inst->base.callback)
|
||||
inst->base.callback(&inst->base, inst->base.context);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Alloc / Free / Reset
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
void* subghz_protocol_decoder_toyota_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderToyota* inst =
|
||||
malloc(sizeof(SubGhzProtocolDecoderToyota));
|
||||
inst->base.protocol = &subghz_protocol_toyota;
|
||||
inst->generic.protocol_name = inst->base.protocol->name;
|
||||
return inst;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_toyota_free(void* context) {
|
||||
furi_assert(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_toyota_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
|
||||
inst->decoder.parser_step = ToyotaStepReset;
|
||||
inst->decoder.te_last = 0;
|
||||
inst->bits_lo = 0;
|
||||
inst->bits_hi = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->te_last = 0;
|
||||
inst->have_high = false;
|
||||
inst->preamble_count = 0;
|
||||
inst->hop = 0;
|
||||
inst->serial = 0;
|
||||
inst->button = 0;
|
||||
/* variant intentionally NOT reset — detected once per session */
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* FEED — Variant A (Corolla 433 MHz)
|
||||
*
|
||||
* PWM pair encoding: LS=0, SL=1.
|
||||
* Preamble: SS pairs until first non-SS pair = first data bit.
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static void toyota_feed_variant_a(
|
||||
SubGhzProtocolDecoderToyota* inst,
|
||||
bool level, uint32_t duration)
|
||||
{
|
||||
const SubGhzBlockConst* c = &toyota_const_a;
|
||||
|
||||
if(inst->decoder.parser_step == ToyotaStepPreambleA) {
|
||||
|
||||
if(level) {
|
||||
inst->te_last = duration;
|
||||
inst->have_high = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!inst->have_high) {
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
inst->have_high = false;
|
||||
|
||||
bool hs = te_is_short(inst->te_last, c);
|
||||
bool hl = te_is_long (inst->te_last, c);
|
||||
bool ls = te_is_short(duration, c);
|
||||
bool ll = te_is_long (duration, c);
|
||||
|
||||
if(hs && ls) {
|
||||
inst->preamble_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
if(inst->preamble_count < TOYOTA_A_PREAMBLE_MIN) {
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
inst->bits_lo = 0;
|
||||
inst->bits_hi = 0;
|
||||
inst->bit_count = 0;
|
||||
|
||||
if (hl && ls) toyota_push_bit(inst, 0);
|
||||
else if(hs && ll) toyota_push_bit(inst, 1);
|
||||
|
||||
inst->decoder.parser_step = ToyotaStepDataA;
|
||||
return;
|
||||
}
|
||||
|
||||
if(inst->decoder.parser_step == ToyotaStepDataA) {
|
||||
|
||||
if(level) {
|
||||
if(te_is_short(duration, c) || te_is_long(duration, c)) {
|
||||
inst->te_last = duration;
|
||||
inst->have_high = true;
|
||||
} else {
|
||||
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found)
|
||||
toyota_decode_and_fire(inst);
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!inst->have_high) return;
|
||||
inst->have_high = false;
|
||||
|
||||
bool hs = te_is_short(inst->te_last, c);
|
||||
bool hl = te_is_long (inst->te_last, c);
|
||||
bool ls = te_is_short(duration, c);
|
||||
bool ll = te_is_long (duration, c);
|
||||
|
||||
if(hl && ls) {
|
||||
toyota_push_bit(inst, 0);
|
||||
} else if(hs && ll) {
|
||||
toyota_push_bit(inst, 1);
|
||||
} else {
|
||||
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found)
|
||||
toyota_decode_and_fire(inst);
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
if(inst->bit_count >= TOYOTA_A_BITS) {
|
||||
toyota_decode_and_fire(inst);
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* FEED — Variant B (Tundra 315 MHz)
|
||||
*
|
||||
* PREAMBLE state:
|
||||
* Processes HIGH+LOW pairs using tight TE matching.
|
||||
* Each [SHORT HIGH + LONG LOW] pair increments preamble_count.
|
||||
* A LOW >= TOYOTA_B_SYNC_GAP_MIN after a valid preamble
|
||||
* transitions to DATA state.
|
||||
*
|
||||
* DATA state — TRUE NRZ:
|
||||
* Every single pulse (HIGH or LOW) independently encodes one bit.
|
||||
* The Flipper delivers them alternating level=true/false.
|
||||
* We process EACH pulse regardless of polarity:
|
||||
* duration <= TOYOTA_B_NRZ_MIDPOINT (287us) -> bit 0
|
||||
* duration > TOYOTA_B_NRZ_MIDPOINT -> bit 1
|
||||
* A pulse >= TOYOTA_B_SYNC_GAP_MIN ends the frame.
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
static void toyota_feed_variant_b(
|
||||
SubGhzProtocolDecoderToyota* inst,
|
||||
bool level, uint32_t duration)
|
||||
{
|
||||
const SubGhzBlockConst* c = &toyota_const_b;
|
||||
|
||||
/* ── PREAMBLE ── */
|
||||
if(inst->decoder.parser_step == ToyotaStepPreambleB) {
|
||||
|
||||
if(level) {
|
||||
if(te_is_short(duration, c)) {
|
||||
inst->te_last = duration;
|
||||
inst->have_high = true;
|
||||
} else {
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Falling edge */
|
||||
if(!inst->have_high) {
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
inst->have_high = false;
|
||||
|
||||
/* Sync gap: LOW ~1938us -> transition to data */
|
||||
if(duration >= TOYOTA_B_SYNC_GAP_MIN &&
|
||||
duration <= TOYOTA_B_SYNC_GAP_MAX)
|
||||
{
|
||||
if(inst->preamble_count >= TOYOTA_B_PREAMBLE_MIN) {
|
||||
FURI_LOG_D(TAG, "B: sync gap after %d pairs -> NRZ data",
|
||||
(int)inst->preamble_count);
|
||||
inst->bits_lo = 0;
|
||||
inst->bits_hi = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->have_high = false;
|
||||
inst->decoder.parser_step = ToyotaStepDataB;
|
||||
} else {
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* Normal preamble LOW must be LONG */
|
||||
if(te_is_long(duration, c)) {
|
||||
inst->preamble_count++;
|
||||
return;
|
||||
}
|
||||
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
/* ── DATA (NRZ) ── */
|
||||
if(inst->decoder.parser_step == ToyotaStepDataB) {
|
||||
|
||||
/*
|
||||
* Every pulse — HIGH or LOW — encodes one bit independently.
|
||||
* A pulse >= sync gap minimum signals end of frame.
|
||||
* A pulse >= inter-frame gap also ends frame.
|
||||
*/
|
||||
if(duration >= TOYOTA_B_SYNC_GAP_MIN) {
|
||||
/* Frame ended by gap */
|
||||
if(inst->bit_count >= (uint8_t)c->min_count_bit_for_found) {
|
||||
toyota_decode_and_fire(inst);
|
||||
}
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* NRZ bit: midpoint classification.
|
||||
* <= 287us -> bit 0
|
||||
* > 287us -> bit 1
|
||||
*/
|
||||
uint8_t bit = (duration > TOYOTA_B_NRZ_MIDPOINT) ? 1 : 0;
|
||||
toyota_push_bit(inst, bit);
|
||||
|
||||
if(inst->bit_count >= TOYOTA_B_BITS) {
|
||||
toyota_decode_and_fire(inst);
|
||||
subghz_protocol_decoder_toyota_reset(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Public feed — dispatcher
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
|
||||
if(inst->decoder.parser_step == ToyotaStepReset) {
|
||||
if(!level) return;
|
||||
|
||||
/*
|
||||
* Variant detection from first SHORT HIGH pulse:
|
||||
* < 310us -> Variant B (Tundra 315MHz, TE_short~200us)
|
||||
* >= 310us -> Variant A (Corolla 433MHz, TE_short~400us)
|
||||
*/
|
||||
bool fits_b = te_is_short(duration, &toyota_const_b) &&
|
||||
(duration < TOYOTA_VARIANT_THRESH);
|
||||
bool fits_a = te_is_short(duration, &toyota_const_a) &&
|
||||
(duration >= TOYOTA_VARIANT_THRESH);
|
||||
|
||||
if(fits_b) {
|
||||
inst->variant = 1;
|
||||
inst->te_last = duration;
|
||||
inst->have_high = true;
|
||||
inst->preamble_count = 0;
|
||||
inst->decoder.parser_step = ToyotaStepPreambleB;
|
||||
FURI_LOG_D(TAG, "Detected Variant B (Tundra), first HIGH=%lu",
|
||||
(unsigned long)duration);
|
||||
} else if(fits_a) {
|
||||
inst->variant = 0;
|
||||
inst->te_last = duration;
|
||||
inst->have_high = true;
|
||||
inst->preamble_count = 0;
|
||||
inst->decoder.parser_step = ToyotaStepPreambleA;
|
||||
FURI_LOG_D(TAG, "Detected Variant A (Corolla), first HIGH=%lu",
|
||||
(unsigned long)duration);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(inst->variant == 1) {
|
||||
toyota_feed_variant_b(inst, level, duration);
|
||||
} else {
|
||||
toyota_feed_variant_a(inst, level, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Hash
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
uint8_t subghz_protocol_decoder_toyota_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&inst->decoder,
|
||||
(inst->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Serialize
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_toyota_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset)
|
||||
{
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
inst->generic.cnt = inst->variant;
|
||||
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* Deserialize
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format)
|
||||
{
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize_check_count_bit(
|
||||
&inst->generic,
|
||||
flipper_format,
|
||||
toyota_const_b.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
inst->hop = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->serial = (uint32_t)((inst->generic.data >> 4) & 0x0FFFFFFF);
|
||||
inst->button = (uint8_t)(inst->generic.data & 0x0F);
|
||||
|
||||
inst->generic.serial = inst->serial;
|
||||
inst->generic.btn = inst->button;
|
||||
inst->variant = (inst->generic.cnt != 0) ? 1 : 0;
|
||||
inst->generic.cnt = inst->variant;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------
|
||||
* get_string
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
void subghz_protocol_decoder_toyota_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderToyota* inst = context;
|
||||
|
||||
uint32_t hop = (uint32_t)(inst->generic.data >> 32);
|
||||
uint32_t serial = (uint32_t)((inst->generic.data >> 4) & 0x0FFFFFFF);
|
||||
uint8_t button = (uint8_t)(inst->generic.data & 0x0F);
|
||||
uint8_t var = (inst->generic.cnt != 0) ? 1 : 0;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Hop: %08lX\r\n"
|
||||
"Sn: %07lX\r\n"
|
||||
"Btn: %X [%s]",
|
||||
toyota_model_name(var),
|
||||
inst->generic.data_count_bit,
|
||||
(unsigned long)hop,
|
||||
(unsigned long)serial,
|
||||
button,
|
||||
toyota_button_name(button, var));
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_TOYOTA_NAME "Toyota"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_toyota;
|
||||
|
||||
void* subghz_protocol_decoder_toyota_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_toyota_free(void* context);
|
||||
void subghz_protocol_decoder_toyota_reset(void* context);
|
||||
void subghz_protocol_decoder_toyota_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_toyota_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_toyota_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_toyota_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_toyota_get_string(void* context, FuriString* output);
|
||||
Reference in New Issue
Block a user