Compare commits

...

10 Commits

Author SHA1 Message Date
d4rks1d33 29fef56be1 Added Ford V1, Kia V7 and Honda Static, thanks RalphWiggum!
Build Dev Firmware / build (push) Successful in 7m1s
2026-04-26 22:09:52 -03:00
Andrea Santaniello 6a348dd304 Revert "some updates"
Build Dev Firmware / build (push) Successful in 7m0s
This reverts commit a55189e2a4.
2026-04-17 18:10:10 +02:00
Andrea Santaniello 32a96e580d Update psa.c
Build Dev Firmware / build (push) Successful in 7m5s
2026-04-17 17:34:55 +02:00
Andrea Santaniello 54f03a39c2 Update psa.c
Build Dev Firmware / build (push) Successful in 7m3s
2026-04-17 16:36:37 +02:00
d4rks1d33 a55189e2a4 some updates
Build Dev Firmware / build (push) Successful in 7m9s
2026-04-16 20:02:54 -03:00
Andrea Santaniello 14d10c0794 Merge branch 'main' of https://github.com/D4C1-Labs/Flipper-ARF
Build Dev Firmware / build (push) Successful in 6m42s
2026-04-07 13:22:35 +02:00
Andrea Santaniello 27818ccb1f Re-enabling GangQi / Disabling Honda until fix. 2026-04-07 13:22:32 +02:00
d4rks1d33 0ebf26eff4 Update Honda, should be less false positives
Build Dev Firmware / build (push) Successful in 6m32s
2026-04-05 20:12:04 -03:00
Andrea Santaniello ac620e2b0e Fix rolling increment on upload
Build Dev Firmware / build (push) Successful in 6m30s
2026-04-05 18:42:44 +02:00
Andrea Santaniello 46115cdf6c Updated Readme and set flag for 315Mhz 2026-04-05 18:40:05 +02:00
16 changed files with 3885 additions and 1115 deletions
+9 -1
View File
@@ -81,4 +81,12 @@ node_modules/
#companion app
/companion
/Flipper-Android-App
/Flipper-Android-App
#WIP not ready to push protocols
lib/subghz/protocols/subghz_protocol_honda_pandora.c
lib/subghz/protocols/honda_rolling.c
lib/subghz/protocols/honda_rolling.h
lib/subghz/protocols/honda_pandora.c
lib/subghz/protocols/honda_pandora.h
+4
View File
@@ -48,6 +48,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
| Ford | Ford V1 | 315/433 MHz | FM | Yes | Yes | Yes |
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
@@ -58,6 +59,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA V7 | 433 MHz | FM | Yes | Yes | Yes |
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
@@ -66,6 +68,8 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
| Honda | Honda Static | 433 MHz | AM | Yes | Yes | No |
### Gate / Access Protocols
+4 -1
View File
@@ -97,7 +97,7 @@ const SubGhzProtocolEncoder subghz_protocol_chrysler_encoder = {
const SubGhzProtocol subghz_protocol_chrysler = {
.name = CHRYSLER_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_chrysler_decoder,
@@ -333,6 +333,9 @@ LevelDuration subghz_protocol_encoder_chrysler_yield(void* context) {
instance->encoder.repeat--;
}
instance->encoder.front = 0;
chrysler_advance_rolling(instance->raw_data);
chrysler_encoder_rebuild(instance);
chrysler_encoder_get_upload(instance);
}
return ret;
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define FORD_PROTOCOL_V1_NAME "Ford V1"
typedef struct SubGhzProtocolDecoderFordV1 SubGhzProtocolDecoderFordV1;
extern const SubGhzProtocol ford_protocol_v1;
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_ford_v1_free(void* context);
void subghz_protocol_decoder_ford_v1_reset(void* context);
void subghz_protocol_decoder_ford_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_ford_v1_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_ford_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_ford_v1_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_ford_v1_stop(void* context);
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context);
extern const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder;
-855
View File
@@ -1,855 +0,0 @@
#include "honda.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolHonda"
static const SubGhzBlockConst subghz_protocol_honda_const = {
.te_short = HONDA_TE_SHORT,
.te_long = HONDA_TE_LONG,
.te_delta = HONDA_TE_DELTA,
.min_count_bit_for_found = HONDA_MIN_BITS,
};
/* ============================================================================
* Pandora rolling-code tables (extracted from firmware @ 0xEFDC)
* Five 16×16 nibble-substitution tables.
* ==========================================================================*/
static const uint8_t honda_table_a[16][16] = { HONDA_TABLE_A };
static const uint8_t honda_table_b[16][16] = { HONDA_TABLE_B };
static const uint8_t honda_table_c[16][16] = { HONDA_TABLE_C };
static const uint8_t honda_table_d[16][16] = { HONDA_TABLE_D };
static const uint8_t honda_table_e[16][16] __attribute__((unused)) = { HONDA_TABLE_E };
/* ============================================================================
* Bit-reverse helpers (mirrors Crypto.Util.Bit_Reverse_Byte @ 0x11AD4)
* ==========================================================================*/
static inline uint8_t _bit_rev8(uint8_t v) {
v = (uint8_t)(((v & 0xF0u) >> 4) | ((v & 0x0Fu) << 4));
v = (uint8_t)(((v & 0xCCu) >> 2) | ((v & 0x33u) << 2));
v = (uint8_t)(((v & 0xAAu) >> 1) | ((v & 0x55u) << 1));
return v;
}
static inline uint8_t _bit_rev4(uint8_t v) {
/* bit-reverse the low 4 bits only */
return (uint8_t)(_bit_rev8(v & 0x0Fu) >> 4);
}
/* ============================================================================
* The 10-byte frame buffer has this layout (matching Pandora RAM):
* buf[0] — header byte (type_b_header<<4 | button) or (button<<4 | serial_hi)
* buf[1] — serial[23:16]
* buf[2] — serial[15:8]
* buf[3] — serial[7:0] / counter cascade
* buf[4] — {serial_low_nibble, counter[23:20]} (type-B layout)
* buf[5] — counter[19:12]
* buf[6] — counter[11:4]
* buf[7] — {mode_nibble, counter[3:0]} mode: 0x2 or 0xC
* buf[8] — checksum (nibble-substituted via tables on each TX)
* buf[9] — extra / padding
*
* counter bytes are buf[5], buf[6], buf[7] (with low nibble of buf[7] being
* the LSN of the counter and the high nibble being the mode indicator).
*
* The increment algorithm:
* 1. bit_rev-increment the low nibble of buf[3] (= counter LSN in Pandora)
* 2. On overflow, cascade to high nibble of buf[3] then to buf[2]
* 3. Dispatch on mode nibble (buf[7]>>4):
* 0x2 → use TABLE_A/TABLE_D for checksum nibble substitution
* 0xC → use TABLE_B/TABLE_D for checksum nibble substitution, flip mode→0x2
* (other branches flip mode and recurse similarly)
*
* For the Flipper port:
* plain +1 on the 24-bit counter with nibble-reversed carry, then re-compute
* the checksum using the appropriate table pair based on the mode nibble.
* ==========================================================================*/
typedef struct {
bool type_b;
uint8_t type_b_header;
uint8_t button;
uint32_t serial;
uint32_t counter;
uint8_t checksum;
uint8_t mode; /* high nibble of buf[7]: 0x2 or 0xC */
} HondaFrameData;
/* Build the 10-byte Pandora buffer from a HondaFrameData */
static void _honda_to_buf(const HondaFrameData* f, uint8_t buf[10]) {
buf[9] = 0x00;
if(!f->type_b) {
buf[0] = (uint8_t)((f->button << 4) | ((f->serial >> 24) & 0x0Fu));
buf[1] = (uint8_t)((f->serial >> 16) & 0xFFu);
buf[2] = (uint8_t)((f->serial >> 8) & 0xFFu);
buf[3] = (uint8_t)( f->serial & 0xFFu);
buf[4] = (uint8_t)((f->counter >> 16) & 0xFFu);
buf[5] = (uint8_t)((f->counter >> 8) & 0xFFu);
buf[6] = (uint8_t)( f->counter & 0xFFu);
/* buf[7]: mode nibble high | counter LSN low — for Type-A counter is in buf[4..6] */
buf[7] = (uint8_t)((f->mode & 0x0Fu) << 4);
buf[8] = f->checksum;
} else {
buf[0] = (uint8_t)((f->type_b_header << 4) | (f->button & 0x0Fu));
buf[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
buf[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
buf[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
buf[4] = (uint8_t)(((f->serial & 0x0Fu) << 4) | ((f->counter >> 20) & 0x0Fu));
buf[5] = (uint8_t)((f->counter >> 12) & 0xFFu);
buf[6] = (uint8_t)((f->counter >> 4) & 0xFFu);
buf[7] = (uint8_t)(((f->mode & 0x0Fu) << 4) | (f->counter & 0x0Fu));
buf[8] = f->checksum;
}
}
/* Uses TABLE_A (or B) for the low nibble and TABLE_D (or A/B high) for the
* high nibble of buf[8], indexed by bit-reversed nibbles of buf[3] (counter
* cascade byte) as per the decompilation. */
static uint8_t _honda_rolling_checksum(const uint8_t buf[10], bool mode_is_c) {
uint8_t cnt_byte = buf[3]; /* the cascade/index byte Pandora uses */
uint8_t prev_csum = buf[8];
/* Choose table pair based on mode (mirrors Pandora's dispatch on buf[7]>>4) */
const uint8_t (*tbl_lo)[16] = mode_is_c ? honda_table_b : honda_table_a;
const uint8_t (*tbl_hi)[16] = honda_table_d;
const uint8_t (*tbl_perm)[16] = honda_table_c;
uint8_t new_lo = prev_csum & 0x0Fu;
uint8_t new_hi = (prev_csum >> 4) & 0x0Fu;
uint8_t idx = _bit_rev8(cnt_byte) & 0x0Fu;
/* Low nibble substitution (mirrors inner loop in Pandora decompile) */
for(uint8_t row = 0; row < 16; row++) {
if(tbl_lo[row][idx] == (prev_csum & 0x0Fu)) {
new_lo = tbl_perm[row][idx];
break;
}
}
/* High nibble substitution */
uint8_t idx_hi = _bit_rev8(cnt_byte >> 4) & 0x0Fu;
for(uint8_t row = 0; row < 16; row++) {
if(tbl_hi[row][idx_hi] == ((prev_csum >> 4) & 0x0Fu)) {
new_hi = tbl_perm[row][idx_hi];
break;
}
}
return (uint8_t)((new_hi << 4) | new_lo);
}
/* Advance counter by 1 using Pandora's bit-reversed nibble arithmetic.
* counter increment section @ 0xEFF0-0xF090 */
static void _honda_counter_increment(HondaFrameData* f) {
uint8_t buf[10];
_honda_to_buf(f, buf);
/* Pandora increments buf[3] low nibble with bit-reverse carry */
uint8_t lo = _bit_rev4(buf[3] & 0x0Fu);
lo = (lo + 1) & 0x0Fu;
buf[3] = (buf[3] & 0xF0u) | _bit_rev4(lo);
/* Carry to high nibble of buf[3] when low overflows (was 0xF) */
if((f->counter & 0x0Fu) == 0x0Fu) {
uint8_t hi = _bit_rev4((buf[3] >> 4) & 0x0Fu);
hi = (hi + 1) & 0x0Fu;
buf[3] = (buf[3] & 0x0Fu) | (uint8_t)(_bit_rev4(hi) << 4);
/* Carry to buf[2] */
if(((f->counter >> 4) & 0x0Fu) == 0x0Fu) {
uint8_t b2lo = _bit_rev4(buf[2] & 0x0Fu);
b2lo = (b2lo + 1) & 0x0Fu;
buf[2] = (buf[2] & 0xF0u) | _bit_rev4(b2lo);
}
}
/* Plain counter +1 */
f->counter = (f->counter + 1u) & 0x00FFFFFFu;
/* Mode flip: 0x2 ↔ 0xC (Pandora flips mode nibble each TX cycle) */
bool mode_was_c = (f->mode == 0xCu);
f->mode = mode_was_c ? 0x2u : 0xCu;
/* Recompute checksum using Pandora's table lookup */
_honda_to_buf(f, buf);
f->checksum = _honda_rolling_checksum(buf, !mode_was_c);
}
/* ============================================================================
* Simple XOR checksum (Type-A static, used for initial decode validation)
* ==========================================================================*/
static uint8_t _honda_xor_checksum(const uint8_t* data) {
uint8_t c = 0;
for(uint8_t i = 0; i < 7; i++) c ^= data[i];
return c;
}
/* ============================================================================
* Bit helpers
* ==========================================================================*/
static uint32_t _bits_get(const uint8_t* data, uint8_t start, uint8_t len) {
uint32_t val = 0;
for(uint8_t i = 0; i < len; i++) {
uint8_t byte_idx = (uint8_t)((start + i) / 8u);
uint8_t bit_idx = (uint8_t)(7u - ((start + i) % 8u));
val = (val << 1) | ((data[byte_idx] >> bit_idx) & 1u);
}
return val;
}
static void _bits_set(uint8_t* data, uint8_t start, uint8_t len, uint32_t val) {
if(!len) return;
for(int8_t i = (int8_t)len - 1; i >= 0; i--) {
uint8_t pos = (uint8_t)(start + (uint8_t)i);
uint8_t byte_idx = (uint8_t)(pos / 8u);
uint8_t bit_idx = (uint8_t)(7u - (pos % 8u));
if(val & 1u)
data[byte_idx] |= (uint8_t)(1u << bit_idx);
else
data[byte_idx] &= (uint8_t)(~(1u << bit_idx));
val >>= 1;
}
}
/* ============================================================================
* Pack / unpack
* ==========================================================================*/
static uint64_t _honda_pack(const HondaFrameData* f) {
uint8_t key[8] = {0};
key[0] = (uint8_t)(((f->type_b ? 1u : 0u) << 7) |
((f->type_b_header & 0x07u) << 4) | (f->button & 0x0Fu));
key[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
key[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
key[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
key[4] = (uint8_t)((f->serial & 0x0Fu) << 4);
key[5] = (uint8_t)((f->counter >> 16) & 0xFFu);
key[6] = (uint8_t)((f->counter >> 8) & 0xFFu);
key[7] = (uint8_t)( f->counter & 0xFFu);
uint64_t out = 0;
for(int i = 0; i < 8; i++) out = (out << 8) | key[i];
return out;
}
static void _honda_unpack(uint64_t raw, HondaFrameData* f) {
uint8_t key[8];
for(int i = 7; i >= 0; i--) {
key[i] = (uint8_t)(raw & 0xFFu);
raw >>= 8;
}
f->type_b = (key[0] >> 7) & 0x01u;
f->type_b_header = (key[0] >> 4) & 0x07u;
f->button = key[0] & 0x0Fu;
f->serial = ((uint32_t)key[1] << 20) | ((uint32_t)key[2] << 12) |
((uint32_t)key[3] << 4) | ((uint32_t)(key[4] >> 4) & 0x0Fu);
f->counter = ((uint32_t)key[5] << 16) | ((uint32_t)key[6] << 8) | (uint32_t)key[7];
f->mode = 0x2u; /* default mode; will be set properly on decode */
/* Recompute XOR checksum */
uint8_t fb[8] = {0};
if(!f->type_b) {
fb[0] = (uint8_t)((f->button << 4) | ((f->serial >> 24) & 0x0Fu));
fb[1] = (uint8_t)((f->serial >> 16) & 0xFFu);
fb[2] = (uint8_t)((f->serial >> 8) & 0xFFu);
fb[3] = (uint8_t)( f->serial & 0xFFu);
fb[4] = (uint8_t)((f->counter >> 16) & 0xFFu);
fb[5] = (uint8_t)((f->counter >> 8) & 0xFFu);
fb[6] = (uint8_t)( f->counter & 0xFFu);
} else {
fb[0] = (uint8_t)((f->type_b_header << 4) | (f->button & 0x0Fu));
fb[1] = (uint8_t)((f->serial >> 20) & 0xFFu);
fb[2] = (uint8_t)((f->serial >> 12) & 0xFFu);
fb[3] = (uint8_t)((f->serial >> 4) & 0xFFu);
fb[4] = (uint8_t)(((f->serial & 0x0Fu) << 4) | ((f->counter >> 20) & 0x0Fu));
fb[5] = (uint8_t)((f->counter >> 12) & 0xFFu);
fb[6] = (uint8_t)((f->counter >> 4) & 0xFFu);
}
f->checksum = _honda_xor_checksum(fb);
}
/* ============================================================================
* Decoder state
* ==========================================================================*/
#define HONDA_HALF_BIT_BUF 512u
typedef enum {
HondaDecoderStepReset = 0,
HondaDecoderStepAccumulate,
} HondaDecoderStep;
typedef struct SubGhzProtocolDecoderHonda {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint8_t half_bits[HONDA_HALF_BIT_BUF];
uint16_t hb_count;
uint16_t consecutive_clean;
HondaFrameData frame;
bool frame_valid;
} SubGhzProtocolDecoderHonda;
/* ============================================================================
* Encoder state
* ==========================================================================*/
#define HONDA_ENC_BUF_SIZE 512u
typedef struct SubGhzProtocolEncoderHonda {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
HondaFrameData frame;
uint8_t active_button;
} SubGhzProtocolEncoderHonda;
const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
const SubGhzProtocol subghz_protocol_honda;
/* ============================================================================
* Duration classifier
* Pandora uses TE_SHORT=250us, TE_LONG=480us from Brand_Auto_Honda_TX
* ==========================================================================*/
static uint8_t _classify_duration(uint32_t abs_dur) {
if(abs_dur >= (HONDA_TE_SHORT - HONDA_TE_DELTA) &&
abs_dur <= (HONDA_TE_SHORT + HONDA_TE_DELTA)) return 1;
if(abs_dur >= (HONDA_TE_LONG - HONDA_TE_DELTA) &&
abs_dur <= (HONDA_TE_LONG + HONDA_TE_DELTA)) return 2;
if(abs_dur >= (HONDA_TE_SHORT - HONDA_TE_DELTA - 30u) &&
abs_dur <= (HONDA_TE_SHORT + HONDA_TE_DELTA + 30u)) return 1;
if(abs_dur >= (HONDA_TE_LONG - HONDA_TE_DELTA - 30u) &&
abs_dur <= (HONDA_TE_LONG + HONDA_TE_DELTA + 30u)) return 2;
return 0;
}
/* ============================================================================
* Manchester decoder
* ==========================================================================*/
static bool _honda_try_decode_polarity(SubGhzProtocolDecoderHonda* inst, bool invert) {
uint8_t* hb = inst->half_bits;
uint16_t cnt = inst->hb_count;
int16_t best_preamble_end = -1;
uint16_t preamble_count = 0;
for(uint16_t i = 1; i < cnt; i++) {
if(hb[i] != hb[i - 1]) {
preamble_count++;
} else {
if(preamble_count >= HONDA_MIN_PREAMBLE_COUNT) {
best_preamble_end = (int16_t)i;
break;
}
preamble_count = 0;
}
}
if(best_preamble_end < 0 && preamble_count >= HONDA_MIN_PREAMBLE_COUNT)
return false; /* preamble only */
if(best_preamble_end < 0)
best_preamble_end = 0;
/* Skip same-level at sync */
uint16_t i = (uint16_t)best_preamble_end;
while(i + 1 < cnt && hb[i] == hb[i + 1]) i++;
/* Manchester decode */
uint8_t decoded[16] = {0};
uint8_t bit_count = 0;
while(i + 1 < cnt && bit_count < 128u) {
uint8_t h0 = hb[i];
uint8_t h1 = hb[i + 1];
if(h0 != h1) {
uint8_t bit_val;
if(!invert)
bit_val = (h0 == 1 && h1 == 0) ? 1u : 0u;
else
bit_val = (h0 == 0 && h1 == 1) ? 1u : 0u;
uint8_t byte_idx = bit_count / 8u;
uint8_t bit_idx = 7u - (bit_count % 8u);
if(bit_val)
decoded[byte_idx] |= (uint8_t)(1u << bit_idx);
else
decoded[byte_idx] &= (uint8_t)(~(1u << bit_idx));
bit_count++;
i += 2;
} else {
i++;
}
}
if(bit_count < HONDA_MIN_BITS) return false;
FURI_LOG_D(
TAG, "pol=%s bits=%u: %02X %02X %02X %02X %02X %02X %02X %02X",
invert ? "INV" : "NOR", bit_count,
decoded[0], decoded[1], decoded[2], decoded[3],
decoded[4], decoded[5], decoded[6], decoded[7]);
/* --- Type-A: [4b btn][28b serial][24b counter][8b csum] = 64 bits --- */
if(bit_count >= 64u) {
uint8_t btn = (uint8_t)_bits_get(decoded, 0, 4);
uint32_t serial = _bits_get(decoded, 4, 28);
uint32_t counter = _bits_get(decoded, 32, 24);
uint8_t csum = (uint8_t)_bits_get(decoded, 56, 8);
uint8_t xor_check = 0;
for(uint8_t b = 0; b < 7; b++) xor_check ^= decoded[b];
if(xor_check == csum ||
(btn <= HONDA_BTN_LOCK2PRESS && btn > 0 &&
serial != 0 && serial != 0xFFFFFFFu &&
__builtin_popcount(xor_check ^ csum) <= 4)) {
inst->frame.type_b = false;
inst->frame.type_b_header = 0;
inst->frame.button = btn;
inst->frame.serial = serial;
inst->frame.counter = counter;
inst->frame.checksum = csum;
inst->frame.mode = 0x2u;
inst->frame_valid = true;
FURI_LOG_I(
TAG, "DECODED TypeA pol=%s btn=%u ser=%07lX cnt=%06lX",
invert ? "INV" : "NOR",
btn, (unsigned long)serial, (unsigned long)counter);
return true;
}
}
/* --- Type-B: [4b hdr][4b btn][28b serial][24b counter][8b csum] = 68 bits --- */
if(bit_count >= 68u) {
uint8_t hdr = (uint8_t)_bits_get(decoded, 0, 4);
uint8_t btn = (uint8_t)_bits_get(decoded, 4, 4);
uint32_t serial = _bits_get(decoded, 8, 28);
uint32_t counter = _bits_get(decoded, 36, 24);
uint8_t csum = (uint8_t)_bits_get(decoded, 60, 8);
uint8_t calc_csum_b = 0;
{
uint8_t fb[7] = {0};
fb[0] = (uint8_t)((hdr << 4) | (btn & 0x0Fu));
fb[1] = (uint8_t)((serial >> 20) & 0xFFu);
fb[2] = (uint8_t)((serial >> 12) & 0xFFu);
fb[3] = (uint8_t)((serial >> 4) & 0xFFu);
fb[4] = (uint8_t)(((serial & 0x0Fu) << 4) | ((counter >> 20) & 0x0Fu));
fb[5] = (uint8_t)((counter >> 12) & 0xFFu);
fb[6] = (uint8_t)((counter >> 4) & 0xFFu);
for(uint8_t _i = 0; _i < 7; _i++) calc_csum_b ^= fb[_i];
}
if(btn <= HONDA_BTN_LOCK2PRESS &&
(calc_csum_b == csum || __builtin_popcount(calc_csum_b ^ csum) <= 1)) {
inst->frame.type_b = true;
inst->frame.type_b_header = hdr;
inst->frame.button = btn;
inst->frame.serial = serial;
inst->frame.counter = counter;
inst->frame.checksum = csum;
inst->frame.mode = (uint8_t)((decoded[7] >> 4) & 0x0Fu);
inst->frame_valid = true;
FURI_LOG_I(
TAG, "DECODED TypeB pol=%s hdr=%u btn=%u ser=%07lX cnt=%06lX",
invert ? "INV" : "NOR",
hdr, btn, (unsigned long)serial, (unsigned long)counter);
return true;
}
}
return false;
}
static bool _honda_try_decode(SubGhzProtocolDecoderHonda* inst) {
if(inst->hb_count < 40u) return false;
if(_honda_try_decode_polarity(inst, true)) return true;
if(_honda_try_decode_polarity(inst, false)) return true;
return false;
}
/* ============================================================================
* Encoder — build Manchester upload buffer
* Uses Pandora timing: preamble 312 cycles × 250us, data bits 480/250us
* ==========================================================================*/
static void _honda_build_upload(SubGhzProtocolEncoderHonda* inst) {
LevelDuration* buf = inst->encoder.upload;
size_t idx = 0;
buf[idx++] = level_duration_make(false, HONDA_GUARD_TIME_US);
for(uint16_t p = 0; p < (uint16_t)(HONDA_MIN_PREAMBLE_COUNT * 2u); p++) {
buf[idx++] = level_duration_make((p & 1u) != 0u, HONDA_TE_SHORT);
}
uint8_t frame[9] = {0};
uint8_t btn = inst->active_button & 0x0Fu;
if(!inst->frame.type_b) {
_bits_set(frame, 0, 4, btn);
_bits_set(frame, 4, 28, inst->frame.serial);
_bits_set(frame, 32, 24, inst->frame.counter);
_bits_set(frame, 56, 8, _honda_xor_checksum(frame));
} else {
_bits_set(frame, 0, 4, inst->frame.type_b_header);
_bits_set(frame, 4, 4, btn);
_bits_set(frame, 8, 28, inst->frame.serial);
_bits_set(frame, 36, 24, inst->frame.counter);
uint8_t cs = 0;
for(uint8_t i = 0; i < 7; i++) cs ^= frame[i];
_bits_set(frame, 60, 8, cs);
}
uint8_t total_bits = inst->frame.type_b ?
(uint8_t)HONDA_FRAME_BITS_B : (uint8_t)HONDA_FRAME_BITS;
/* Manchester encode inverted: bit-1 = LOW/HIGH, bit-0 = HIGH/LOW, all at TE_SHORT */
for(uint8_t b = 0; b < total_bits; b++) {
uint8_t byte_idx = b / 8u;
uint8_t bit_idx = 7u - (b % 8u);
uint8_t bit = (frame[byte_idx] >> bit_idx) & 1u;
if(bit) {
/* bit 1: LOW then HIGH */
buf[idx++] = level_duration_make(false, HONDA_TE_SHORT);
buf[idx++] = level_duration_make(true, HONDA_TE_SHORT);
} else {
/* bit 0: HIGH then LOW */
buf[idx++] = level_duration_make(true, HONDA_TE_SHORT);
buf[idx++] = level_duration_make(false, HONDA_TE_SHORT);
}
furi_check(idx < HONDA_ENC_BUF_SIZE);
}
buf[idx++] = level_duration_make(false, HONDA_GUARD_TIME_US);
inst->encoder.size_upload = idx;
inst->encoder.front = 0;
}
/* ============================================================================
* Protocol tables
* ==========================================================================*/
const SubGhzProtocolDecoder subghz_protocol_honda_decoder = {
.alloc = subghz_protocol_decoder_honda_alloc,
.free = subghz_protocol_decoder_honda_free,
.feed = subghz_protocol_decoder_honda_feed,
.reset = subghz_protocol_decoder_honda_reset,
.get_hash_data = subghz_protocol_decoder_honda_get_hash_data,
.serialize = subghz_protocol_decoder_honda_serialize,
.deserialize = subghz_protocol_decoder_honda_deserialize,
.get_string = subghz_protocol_decoder_honda_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_honda_encoder = {
.alloc = subghz_protocol_encoder_honda_alloc,
.free = subghz_protocol_encoder_honda_free,
.deserialize = subghz_protocol_encoder_honda_deserialize,
.stop = subghz_protocol_encoder_honda_stop,
.yield = subghz_protocol_encoder_honda_yield,
};
const SubGhzProtocol subghz_protocol_honda = {
.name = SUBGHZ_PROTOCOL_HONDA_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_honda_decoder,
.encoder = &subghz_protocol_honda_encoder,
};
/* ============================================================================
* Custom button helpers
* 1 → Lock (0x01)
* 2 → Unlock (0x02)
* 3 → Trunk (0x04)
* 4 → Panic (0x08)
* 5 → RStart (0x05)
* ==========================================================================*/
uint8_t subghz_protocol_honda_btn_to_custom(uint8_t btn) {
switch(btn) {
case HONDA_BTN_LOCK: return 1;
case HONDA_BTN_UNLOCK: return 2;
case HONDA_BTN_TRUNK: return 3;
case HONDA_BTN_PANIC: return 4;
case HONDA_BTN_RSTART: return 5;
default: return 1;
}
}
uint8_t subghz_protocol_honda_custom_to_btn(uint8_t custom) {
switch(custom) {
case 1: return HONDA_BTN_LOCK;
case 2: return HONDA_BTN_UNLOCK;
case 3: return HONDA_BTN_TRUNK;
case 4: return HONDA_BTN_PANIC;
case 5: return HONDA_BTN_RSTART;
default: return HONDA_BTN_LOCK;
}
}
/* ============================================================================
* Decoder
* ==========================================================================*/
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHonda* inst = malloc(sizeof(SubGhzProtocolDecoderHonda));
furi_check(inst);
memset(inst, 0, sizeof(SubGhzProtocolDecoderHonda));
inst->base.protocol = &subghz_protocol_honda;
inst->generic.protocol_name = inst->base.protocol->name;
inst->frame_valid = false;
FURI_LOG_I(TAG, "decoder allocated");
return inst;
}
void subghz_protocol_decoder_honda_free(void* context) {
furi_assert(context);
free(context);
}
void subghz_protocol_decoder_honda_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
inst->decoder.parser_step = HondaDecoderStepReset;
inst->decoder.te_last = 0;
inst->hb_count = 0;
inst->consecutive_clean = 0;
/* DO NOT clear frame/frame_valid — get_string needs them after reset */
}
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
uint8_t lvl = level ? 1u : 0u;
uint8_t dur_class = _classify_duration(duration);
if(dur_class > 0) {
inst->consecutive_clean++;
if(dur_class == 1) {
if(inst->hb_count < HONDA_HALF_BIT_BUF)
inst->half_bits[inst->hb_count++] = lvl;
} else {
if(inst->hb_count + 2u <= HONDA_HALF_BIT_BUF) {
inst->half_bits[inst->hb_count++] = lvl;
inst->half_bits[inst->hb_count++] = lvl;
}
}
} else {
if(inst->hb_count >= (HONDA_MIN_PREAMBLE_COUNT + 16u)) {
if(_honda_try_decode(inst)) {
inst->generic.data = _honda_pack(&inst->frame);
inst->generic.data_count_bit = inst->frame.type_b ?
(uint8_t)HONDA_FRAME_BITS_B : (uint8_t)HONDA_FRAME_BITS;
inst->generic.serial = inst->frame.serial;
inst->generic.btn = inst->frame.button;
inst->generic.cnt = inst->frame.counter;
FURI_LOG_I(
TAG, "FRAME btn=%u ser=%07lX cnt=%06lX",
inst->frame.button,
(unsigned long)inst->frame.serial,
(unsigned long)inst->frame.counter);
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
if(subghz_custom_btn_get_original() == 0)
subghz_custom_btn_set_original(custom);
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
if(inst->base.callback)
inst->base.callback(&inst->base, inst->base.context);
}
}
inst->hb_count = 0;
inst->consecutive_clean = 0;
}
inst->decoder.te_last = duration;
}
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24) ^
(inst->generic.data >> 32));
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format,
subghz_protocol_honda_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
_honda_unpack(inst->generic.data, &inst->frame);
inst->frame_valid = true;
inst->generic.serial = inst->frame.serial;
inst->generic.btn = inst->frame.button;
inst->generic.cnt = inst->frame.counter;
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
if(subghz_custom_btn_get_original() == 0)
subghz_custom_btn_set_original(custom);
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
FURI_LOG_I(
TAG, "deserialize: btn=%u ser=%07lX cnt=%06lX",
inst->frame.button,
(unsigned long)inst->frame.serial,
(unsigned long)inst->frame.counter);
}
return ret;
}
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderHonda* inst = context;
if(!inst->frame_valid && inst->generic.data != 0) {
_honda_unpack(inst->generic.data, &inst->frame);
inst->frame_valid = true;
}
const char* btn_name;
switch(inst->frame.button) {
case HONDA_BTN_LOCK: btn_name = "Lock"; break;
case HONDA_BTN_UNLOCK: btn_name = "Unlock"; break;
case HONDA_BTN_TRUNK: btn_name = "Trunk/Hatch"; break;
case HONDA_BTN_PANIC: btn_name = "Panic"; break;
case HONDA_BTN_RSTART: btn_name = "Remote Start"; break;
case HONDA_BTN_LOCK2PRESS: btn_name = "Lock x2"; break;
default: btn_name = "Unknown"; break;
}
furi_string_cat_printf(
output,
"%s %s %ubit\r\n"
"Btn:%s (0x%X)\r\n"
"Ser:%07lX\r\n"
"Cnt:%06lX Chk:%02X Mode:%X\r\n",
inst->generic.protocol_name,
inst->frame.type_b ? "TB" : "TA",
inst->generic.data_count_bit,
btn_name,
inst->frame.button,
(unsigned long)inst->frame.serial,
(unsigned long)inst->frame.counter,
inst->frame.checksum,
inst->frame.mode);
}
/* ============================================================================
* Encoder
* ==========================================================================*/
void* subghz_protocol_encoder_honda_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHonda* inst = malloc(sizeof(SubGhzProtocolEncoderHonda));
furi_check(inst);
memset(inst, 0, sizeof(SubGhzProtocolEncoderHonda));
inst->base.protocol = &subghz_protocol_honda;
inst->generic.protocol_name = inst->base.protocol->name;
inst->encoder.repeat = 3;
inst->encoder.size_upload = 0;
inst->encoder.upload = malloc(HONDA_ENC_BUF_SIZE * sizeof(LevelDuration));
furi_check(inst->encoder.upload);
inst->encoder.is_running = false;
inst->encoder.front = 0;
return inst;
}
void subghz_protocol_encoder_honda_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHonda* inst = context;
free(inst->encoder.upload);
free(inst);
}
void subghz_protocol_encoder_honda_stop(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHonda* inst = context;
inst->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_honda_yield(void* context) {
furi_assert(context);
SubGhzProtocolEncoderHonda* inst = context;
if(inst->encoder.repeat == 0 || !inst->encoder.is_running) {
inst->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = inst->encoder.upload[inst->encoder.front];
if(++inst->encoder.front >= inst->encoder.size_upload) {
inst->encoder.repeat--;
inst->encoder.front = 0;
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_encoder_honda_deserialize(
void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderHonda* inst = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&inst->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) return ret;
_honda_unpack(inst->generic.data, &inst->frame);
uint8_t custom = subghz_protocol_honda_btn_to_custom(inst->frame.button);
if(subghz_custom_btn_get_original() == 0)
subghz_custom_btn_set_original(custom);
subghz_custom_btn_set_max(HONDA_CUSTOM_BTN_MAX);
uint8_t active_custom = subghz_custom_btn_get();
inst->active_button = (active_custom == SUBGHZ_CUSTOM_BTN_OK)
? subghz_protocol_honda_custom_to_btn(subghz_custom_btn_get_original())
: subghz_protocol_honda_custom_to_btn(active_custom);
inst->frame.counter = (inst->frame.counter +
furi_hal_subghz_get_rolling_counter_mult()) & 0x00FFFFFFu;
_honda_counter_increment(&inst->frame);
inst->frame.button = inst->active_button;
inst->generic.data = _honda_pack(&inst->frame);
inst->generic.cnt = inst->frame.counter;
inst->generic.btn = inst->active_button;
flipper_format_rewind(flipper_format);
uint8_t key_data[8];
for(int i = 0; i < 8; i++)
key_data[i] = (uint8_t)(inst->generic.data >> (56 - i * 8));
flipper_format_update_hex(flipper_format, "Key", key_data, 8);
_honda_build_upload(inst);
inst->encoder.is_running = true;
return SubGhzProtocolStatusOk;
}
void subghz_protocol_encoder_honda_set_button(void* context, uint8_t btn) {
furi_assert(context);
SubGhzProtocolEncoderHonda* inst = context;
inst->active_button = btn & 0x0Fu;
inst->encoder.is_running = false;
_honda_counter_increment(&inst->frame);
inst->generic.data = _honda_pack(&inst->frame);
inst->generic.cnt = inst->frame.counter;
_honda_build_upload(inst);
inst->encoder.repeat = 3;
inst->encoder.is_running = true;
}
-169
View File
@@ -1,169 +0,0 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#define SUBGHZ_PROTOCOL_HONDA_NAME "Honda"
#define HONDA_TE_SHORT 63u
#define HONDA_TE_LONG 126u
#define HONDA_TE_DELTA 35u
#define HONDA_GUARD_TIME_US 700u
#define HONDA_MIN_PREAMBLE_COUNT 20u
#define HONDA_PREAMBLE_CYCLES 312u
#define HONDA_FRAME_BITS 64u
#define HONDA_FRAME_BITS_B 68u
#define HONDA_MIN_BITS HONDA_FRAME_BITS
#define HONDA_BTN_LOCK 0x01u
#define HONDA_BTN_UNLOCK 0x02u
#define HONDA_BTN_TRUNK 0x04u
#define HONDA_BTN_PANIC 0x08u
#define HONDA_BTN_RSTART 0x05u
#define HONDA_BTN_LOCK2PRESS 0x09u
#define HONDA_TABLE_A \
{0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D,0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09}, \
{0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07,0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03}, \
{0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00,0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04}, \
{0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A,0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E}, \
{0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B,0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F}, \
{0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01,0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05}, \
{0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06,0x0D,0x09,0x0F,0x0B,0x04,0x00,0x06,0x02}, \
{0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C,0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08}, \
{0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E,0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A}, \
{0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04,0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00}, \
{0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03,0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07}, \
{0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09,0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D}, \
{0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08,0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C}, \
{0x0D,0x09,0x0F,0x0B,0x09,0x00,0x06,0x02,0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06}, \
{0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05,0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01}, \
{0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F,0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B}
#define HONDA_TABLE_B \
{0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03,0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07}, \
{0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09,0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D}, \
{0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E,0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A}, \
{0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04,0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00}, \
{0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05,0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01}, \
{0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F,0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B}, \
{0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08,0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C}, \
{0x0D,0x09,0x0F,0x0B,0x09,0x00,0x06,0x02,0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06}, \
{0x0F,0x0B,0x0D,0x09,0x06,0x02,0x04,0x00,0x0B,0x0F,0x09,0x0D,0x02,0x06,0x00,0x04}, \
{0x05,0x01,0x07,0x03,0x0C,0x08,0x0E,0x0A,0x01,0x05,0x03,0x07,0x08,0x0C,0x0A,0x0E}, \
{0x02,0x06,0x00,0x04,0x0B,0x0F,0x09,0x0D,0x06,0x02,0x04,0x00,0x0F,0x0B,0x0D,0x09}, \
{0x08,0x0C,0x0A,0x0E,0x01,0x05,0x03,0x07,0x0C,0x08,0x0E,0x0A,0x05,0x01,0x07,0x03}, \
{0x09,0x0D,0x0B,0x0F,0x00,0x04,0x02,0x06,0x0D,0x09,0x0F,0x0B,0x04,0x00,0x06,0x02}, \
{0x03,0x07,0x01,0x05,0x0A,0x0E,0x08,0x0C,0x07,0x03,0x05,0x01,0x0E,0x0A,0x0C,0x08}, \
{0x04,0x00,0x06,0x02,0x0D,0x09,0x0F,0x0B,0x00,0x04,0x02,0x06,0x09,0x0D,0x0B,0x0F}, \
{0x0E,0x0A,0x0C,0x08,0x07,0x03,0x05,0x01,0x0A,0x0E,0x08,0x0C,0x03,0x07,0x01,0x05}
#define HONDA_TABLE_C \
{0x02,0x08,0x0F,0x05,0x04,0x0E,0x09,0x03,0x01,0x0B,0x0C,0x06,0x07,0x0D,0x0A,0x00}, \
{0x0B,0x01,0x06,0x0C,0x0D,0x07,0x00,0x0A,0x08,0x02,0x05,0x0F,0x0E,0x04,0x03,0x09}, \
{0x06,0x0C,0x0B,0x01,0x00,0x0A,0x0D,0x07,0x05,0x0F,0x08,0x02,0x03,0x09,0x0E,0x04}, \
{0x0F,0x05,0x02,0x08,0x09,0x03,0x04,0x0E,0x0C,0x06,0x01,0x0B,0x0A,0x00,0x07,0x0D}, \
{0x08,0x02,0x05,0x0F,0x0E,0x04,0x03,0x09,0x0B,0x01,0x06,0x0C,0x0D,0x07,0x00,0x0A}, \
{0x01,0x0B,0x0C,0x06,0x07,0x0D,0x0A,0x00,0x02,0x08,0x0F,0x05,0x04,0x0E,0x09,0x03}, \
{0x0C,0x06,0x01,0x0B,0x0A,0x00,0x07,0x0D,0x0F,0x05,0x02,0x08,0x09,0x03,0x04,0x0E}, \
{0x05,0x0F,0x08,0x02,0x03,0x09,0x0E,0x04,0x06,0x0C,0x0B,0x01,0x00,0x0A,0x0D,0x07}, \
{0x09,0x03,0x04,0x0E,0x0F,0x05,0x02,0x08,0x0A,0x00,0x07,0x0D,0x0C,0x06,0x01,0x0B}, \
{0x00,0x0A,0x0D,0x07,0x06,0x0C,0x0B,0x01,0x03,0x09,0x0E,0x04,0x05,0x0F,0x08,0x02}, \
{0x0D,0x07,0x00,0x0A,0x0B,0x01,0x06,0x0C,0x0E,0x04,0x03,0x09,0x08,0x02,0x05,0x0F}, \
{0x04,0x0E,0x09,0x03,0x02,0x08,0x0F,0x05,0x07,0x0D,0x0A,0x00,0x01,0x0B,0x0C,0x06}, \
{0x03,0x09,0x0E,0x04,0x05,0x0F,0x08,0x02,0x00,0x0A,0x0D,0x07,0x06,0x0C,0x0B,0x01}, \
{0x0A,0x00,0x07,0x0D,0x0C,0x06,0x01,0x0B,0x09,0x03,0x04,0x0E,0x0F,0x05,0x02,0x08}, \
{0x07,0x0D,0x0A,0x00,0x01,0x0B,0x0C,0x06,0x04,0x0E,0x09,0x03,0x02,0x08,0x0F,0x05}, \
{0x0E,0x04,0x03,0x09,0x08,0x02,0x05,0x0F,0x0D,0x07,0x00,0x0A,0x0B,0x01,0x06,0x0C}
#define HONDA_TABLE_D \
{0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F,0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04}, \
{0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E,0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05}, \
{0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B,0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00}, \
{0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A,0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01}, \
{0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05,0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E}, \
{0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04,0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F}, \
{0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01,0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A}, \
{0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00,0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B}, \
{0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A,0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01}, \
{0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B,0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00}, \
{0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E,0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05}, \
{0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F,0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04}, \
{0x09,0x03,0x0C,0x06,0x0F,0x05,0x0A,0x00,0x02,0x08,0x07,0x0D,0x04,0x0E,0x01,0x0B}, \
{0x08,0x02,0x0D,0x07,0x0E,0x04,0x0B,0x01,0x03,0x09,0x06,0x0C,0x05,0x0F,0x00,0x0A}, \
{0x0D,0x07,0x08,0x02,0x0B,0x01,0x0E,0x04,0x06,0x0C,0x03,0x09,0x00,0x0A,0x05,0x0F}, \
{0x0C,0x06,0x09,0x03,0x0A,0x00,0x0F,0x05,0x07,0x0D,0x02,0x08,0x01,0x0B,0x04,0x0E}
#define HONDA_TABLE_E \
{0x01,0x00,0x05,0x04,0x0B,0x0A,0x0F,0x0E,0x04,0x05,0x00,0x01,0x0E,0x0F,0x0A,0x0B}, \
{0x0F,0x0E,0x0B,0x0A,0x05,0x04,0x01,0x00,0x0A,0x0B,0x0E,0x0F,0x00,0x01,0x04,0x05}, \
{0x0E,0x0F,0x0A,0x0B,0x04,0x05,0x00,0x01,0x0B,0x0A,0x0F,0x0E,0x01,0x00,0x05,0x04}, \
{0x00,0x01,0x04,0x05,0x0A,0x0B,0x0E,0x0F,0x05,0x04,0x01,0x00,0x0F,0x0E,0x0B,0x0A}, \
{0x02,0x03,0x06,0x07,0x08,0x09,0x0C,0x0D,0x07,0x06,0x03,0x02,0x0D,0x0C,0x09,0x08}, \
{0x0C,0x0D,0x08,0x09,0x06,0x07,0x02,0x03,0x09,0x08,0x0D,0x0C,0x03,0x02,0x07,0x06}, \
{0x0D,0x0C,0x09,0x08,0x07,0x06,0x03,0x02,0x08,0x09,0x0C,0x0D,0x02,0x03,0x06,0x07}, \
{0x03,0x02,0x07,0x06,0x09,0x08,0x0D,0x0C,0x06,0x07,0x02,0x03,0x0C,0x0D,0x08,0x09}, \
{0x04,0x05,0x00,0x01,0x0E,0x0F,0x0A,0x0B,0x01,0x00,0x05,0x04,0x0B,0x0A,0x0F,0x0E}, \
{0x0A,0x0B,0x0E,0x0F,0x00,0x01,0x04,0x05,0x0F,0x0E,0x0B,0x0A,0x05,0x04,0x01,0x00}, \
{0x0B,0x0A,0x0F,0x0E,0x01,0x00,0x05,0x04,0x0E,0x0F,0x0A,0x0B,0x04,0x05,0x00,0x01}, \
{0x05,0x04,0x01,0x00,0x0F,0x0E,0x0B,0x0A,0x00,0x01,0x04,0x05,0x0A,0x0B,0x0E,0x0F}, \
{0x07,0x06,0x03,0x02,0x0D,0x0C,0x09,0x08,0x02,0x03,0x06,0x07,0x08,0x09,0x0C,0x0D}, \
{0x09,0x08,0x0D,0x0C,0x03,0x02,0x07,0x06,0x0C,0x0D,0x08,0x09,0x06,0x07,0x02,0x03}, \
{0x08,0x09,0x0C,0x0D,0x02,0x0A,0x06,0x07,0x0D,0x0C,0x09,0x08,0x07,0x06,0x03,0x02}, \
{0x06,0x07,0x02,0x03,0x0C,0x0D,0x08,0x09,0x03,0x02,0x07,0x06,0x09,0x08,0x0D,0x0C}
#define HONDA_CC1101_PRESET_DATA \
0x02, 0x0D, \
0x0B, 0x06, \
0x08, 0x32, \
0x07, 0x04, \
0x14, 0x00, \
0x13, 0x02, \
0x12, 0x04, \
0x11, 0x36, \
0x10, 0x69, \
0x15, 0x32, \
0x18, 0x18, \
0x19, 0x16, \
0x1D, 0x91, \
0x1C, 0x00, \
0x1B, 0x07, \
0x20, 0xFB, \
0x22, 0x10, \
0x21, 0x56, \
0x00, 0x00, \
0xC0, 0x00
#define HONDA_CUSTOM_BTN_MAX 5
extern const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
extern const SubGhzProtocol subghz_protocol_honda;
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_free(void* context);
void subghz_protocol_decoder_honda_reset(void* context);
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset);
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_free(void* context);
void subghz_protocol_encoder_honda_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_yield(void* context);
SubGhzProtocolStatus subghz_protocol_encoder_honda_deserialize(
void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_set_button(void* context, uint8_t btn);
uint8_t subghz_protocol_honda_btn_to_custom(uint8_t btn);
uint8_t subghz_protocol_honda_custom_to_btn(uint8_t custom);
+796
View File
@@ -0,0 +1,796 @@
#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;
}
+811
View File
@@ -0,0 +1,811 @@
#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;
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/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);
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/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);
+711
View File
@@ -0,0 +1,711 @@
#include "kia_v7.h"
#include <string.h>
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
#define KIA_V7_PREAMBLE_PAIRS 0x13F
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
#define KIA_V7_HEADER 0x4C
#define KIA_V7_TAIL_GAP_US 0x7D0
#define KIA_V7_KEY_BITS 64U
#define KIA_V7_DEFAULT_TX_REPEAT 10U
static const SubGhzBlockConst subghz_protocol_kia_v7_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = KIA_V7_KEY_BITS,
};
typedef enum {
KiaV7DecoderStepReset = 0,
KiaV7DecoderStepPreamble = 1,
KiaV7DecoderStepSyncLow = 2,
KiaV7DecoderStepData = 3,
} KiaV7DecoderStep;
struct SubGhzProtocolDecoderKiaV7 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint16_t preamble_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
struct SubGhzProtocolEncoderKiaV7 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint8_t tx_bit_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x4CU;
for(size_t index = 0; index < len; index++) {
crc ^= data[index];
for(uint8_t bit = 0; bit < 8; bit++) {
const bool msb = (crc & 0x80U) != 0U;
crc <<= 1U;
if(msb) {
crc ^= 0x7FU;
}
}
}
return crc;
}
static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
for(size_t index = 0; index < 8; index++) {
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
}
}
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t data = 0;
for(size_t index = 0; index < 8; index++) {
data = (data << 8U) | bytes[index];
}
return data;
}
static bool kia_v7_is_short(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_short) <
subghz_protocol_kia_v7_const.te_delta;
}
static bool kia_v7_is_long(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_long) <
subghz_protocol_kia_v7_const.te_delta;
}
static const char* kia_v7_get_button_name(uint8_t button) {
switch(button) {
case 0x01:
return "LOCK";
case 0x02:
return "UNLOCK";
case 0x03:
case 0x08:
return "BOOT";
default:
return "??";
}
}
static SubGhzProtocolStatus
kia_v7_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) {
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
FuriString* display = furi_string_alloc();
furi_string_printf(display, "%s - %s", protocol_name, kia_v7_get_button_name(button));
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
status = SubGhzProtocolStatusErrorParserOthers;
}
furi_string_free(display);
return status;
}
static void kia_v7_decode_key_common(
SubGhzBlockGeneric* generic,
uint8_t* decoded_button,
uint8_t* fixed_high_byte,
uint8_t* crc_calculated,
uint8_t* crc_raw,
bool* crc_valid) {
uint8_t bytes[8];
kia_v7_u64_to_bytes_be(generic->data, bytes);
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
const uint8_t button = bytes[6] & 0x0FU;
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
const uint8_t crc_pkt = bytes[7];
generic->serial = serial & 0x0FFFFFFFU;
generic->btn = button;
generic->cnt = counter;
generic->data_count_bit = KIA_V7_KEY_BITS;
if(decoded_button) {
*decoded_button = button;
}
if(fixed_high_byte) {
*fixed_high_byte = bytes[0];
}
if(crc_calculated) {
*crc_calculated = crc_calc;
}
if(crc_raw) {
*crc_raw = crc_pkt;
}
if(crc_valid) {
*crc_valid = (crc_calc == crc_pkt);
}
}
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static uint64_t kia_v7_encode_key(
uint8_t fixed_high_byte,
uint32_t serial,
uint8_t button,
uint16_t counter,
uint8_t* crc_out) {
uint8_t bytes[8];
serial &= 0x0FFFFFFFU;
button &= 0x0FU;
bytes[0] = fixed_high_byte;
bytes[1] = (counter >> 8U) & 0xFFU;
bytes[2] = counter & 0xFFU;
bytes[3] = (serial >> 20U) & 0xFFU;
bytes[4] = (serial >> 12U) & 0xFFU;
bytes[5] = (serial >> 4U) & 0xFFU;
bytes[6] = ((serial & 0x0FU) << 4U) | button;
bytes[7] = kia_v7_crc8(bytes, 7);
if(crc_out) {
*crc_out = bytes[7];
}
return kia_v7_bytes_to_u64_be(bytes);
}
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
furi_check(instance);
const LevelDuration high_short =
level_duration_make(true, subghz_protocol_kia_v7_const.te_short);
const LevelDuration low_short =
level_duration_make(false, subghz_protocol_kia_v7_const.te_short);
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
instance->tx_bit_count :
64U;
size_t final_size = 0;
for(uint8_t pass = 0; pass < 2; pass++) {
size_t index = pass;
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_short;
}
if((index + 1U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
if((index + 2U) > max_size) {
return false;
}
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
instance->encoder.upload[index++] = value ? high_short : low_short;
instance->encoder.upload[index++] = value ? low_short : high_short;
}
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_tail;
final_size = index;
}
instance->encoder.front = 0;
instance->encoder.size_upload = final_size;
return true;
}
const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder = {
.alloc = kia_protocol_decoder_v7_alloc,
.free = kia_protocol_decoder_v7_free,
.feed = kia_protocol_decoder_v7_feed,
.reset = kia_protocol_decoder_v7_reset,
.get_hash_data = kia_protocol_decoder_v7_get_hash_data,
.serialize = kia_protocol_decoder_v7_serialize,
.deserialize = kia_protocol_decoder_v7_deserialize,
.get_string = kia_protocol_decoder_v7_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder = {
.alloc = kia_protocol_encoder_v7_alloc,
.free = kia_protocol_encoder_v7_free,
.deserialize = kia_protocol_encoder_v7_deserialize,
.stop = kia_protocol_encoder_v7_stop,
.yield = kia_protocol_encoder_v7_yield,
};
const SubGhzProtocol subghz_protocol_kia_v7 = {
.name = KIA_PROTOCOL_V7_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v7_decoder,
.encoder = &subghz_protocol_kia_v7_encoder,
};
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
furi_check(instance);
instance->base.protocol = &subghz_protocol_kia_v7;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 1;
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
instance->encoder.upload = malloc(KIA_V7_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void kia_protocol_encoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
do {
FuriString* temp_str = furi_string_alloc();
if(!temp_str) {
break;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
furi_string_free(temp_str);
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
furi_string_free(temp_str);
break;
}
furi_string_free(temp_str);
flipper_format_rewind(flipper_format);
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
if(load_st != SubGhzProtocolStatusOk) {
break;
}
instance->tx_bit_count =
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
(uint8_t)instance->generic.data_count_bit :
64U;
kia_v7_decode_key_encoder(instance);
uint32_t u32 = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) {
instance->generic.serial = u32;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
instance->generic.btn = (uint8_t)u32;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
instance->generic.cnt = (uint16_t)u32;
}
instance->generic.btn &= 0x0FU;
instance->generic.cnt &= 0xFFFFU;
instance->generic.serial &= 0x0FFFFFFFU;
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn,
(uint16_t)instance->generic.cnt,
&instance->crc_calculated);
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Repeat", &u32, 1)) {
u32 = KIA_V7_DEFAULT_TX_REPEAT;
}
instance->encoder.repeat = u32;
if(!kia_v7_encoder_get_upload(instance)) {
break;
}
if(instance->encoder.size_upload == 0) {
break;
}
flipper_format_rewind(flipper_format);
uint8_t key_data[sizeof(uint64_t)];
kia_v7_u64_to_bytes_be(instance->generic.data, key_data);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
break;
}
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void kia_protocol_encoder_v7_stop(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
instance->encoder.is_running = false;
}
LevelDuration kia_protocol_encoder_v7_yield(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return duration;
}
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
furi_check(instance);
instance->base.protocol = &subghz_protocol_kia_v7;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void kia_protocol_decoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
free(instance);
}
void kia_protocol_decoder_v7_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
instance->decoder.parser_step = KiaV7DecoderStepReset;
instance->decoder.te_last = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
ManchesterEvent event = ManchesterEventReset;
bool data = false;
switch(instance->decoder.parser_step) {
case KiaV7DecoderStepReset:
if(level && kia_v7_is_short(duration)) {
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
instance->decoder.te_last = duration;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case KiaV7DecoderStepPreamble:
if(level) {
if(kia_v7_is_long(duration) && kia_v7_is_short(instance->decoder.te_last)) {
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else if(kia_v7_is_short(duration)) {
instance->decoder.te_last = duration;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else {
if(kia_v7_is_short(duration) && kia_v7_is_short(instance->decoder.te_last)) {
instance->preamble_count++;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
}
break;
case KiaV7DecoderStepSyncLow:
if(!level && kia_v7_is_short(duration) && kia_v7_is_long(instance->decoder.te_last)) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepData;
}
break;
case KiaV7DecoderStepData: {
if(kia_v7_is_short(duration)) {
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
} else if(kia_v7_is_long(duration)) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
event = ManchesterEventReset;
}
if(kia_v7_is_short(duration) || kia_v7_is_long(duration)) {
if(manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data)) {
subghz_protocol_blocks_add_bit(&instance->decoder, data);
}
}
if(instance->decoder.decode_count_bit == KIA_V7_KEY_BITS) {
const uint64_t candidate = ~instance->decoder.decode_data;
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
if(hdr == KIA_V7_HEADER) {
instance->generic.data = candidate;
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
kia_v7_decode_key_decoder(instance);
if(instance->crc_valid) {
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
} else {
instance->generic.data = 0;
instance->generic.data_count_bit = 0;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
} else {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
}
break;
}
}
}
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit >> 3U) + 1U);
}
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%01X [%s] CRC:%02X [%s]",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->generic.data,
instance->generic.serial & 0x0FFFFFFFU,
instance->generic.cnt & 0xFFFFU,
instance->decoded_button & 0x0FU,
kia_v7_get_button_name(instance->decoded_button),
instance->crc_calculated,
instance->crc_valid ? "OK" : "ERR");
}
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
uint32_t serial = instance->generic.serial & 0x0FFFFFFFU;
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t btn_u32 = (uint32_t)(instance->decoded_button & 0x0FU);
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t cnt_u32 = (uint32_t)(instance->generic.cnt & 0xFFFFU);
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
if(!flipper_format_write_uint32(flipper_format, "Repeat", &repeat_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return kia_v7_write_display(
flipper_format, instance->generic.protocol_name, instance->decoded_button);
}
SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(!flipper_format_rewind(flipper_format)) {
return SubGhzProtocolStatusErrorParserOthers;
}
kia_v7_decode_key_decoder(instance);
uint32_t ser_u32 = 0;
uint32_t btn_u32 = 0;
uint32_t cnt_u32 = 0;
bool got_serial = false;
bool got_btn = false;
bool got_cnt = false;
flipper_format_rewind(flipper_format);
got_serial = flipper_format_read_uint32(flipper_format, "Serial", &ser_u32, 1);
flipper_format_rewind(flipper_format);
got_btn = flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
flipper_format_rewind(flipper_format);
got_cnt = flipper_format_read_uint32(flipper_format, "Cnt", &cnt_u32, 1);
if(got_serial || got_btn || got_cnt) {
if(got_serial) {
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
}
if(got_btn) {
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
}
if(got_cnt) {
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
}
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn & 0x0FU,
(uint16_t)(instance->generic.cnt & 0xFFFFU),
&instance->crc_calculated);
kia_v7_decode_key_decoder(instance);
}
return SubGhzProtocolStatusOk;
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#include <lib/toolbox/manchester_decoder.h>
#define KIA_PROTOCOL_V7_NAME "Kia V7"
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
extern const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder;
extern const SubGhzProtocol subghz_protocol_kia_v7;
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment);
void kia_protocol_decoder_v7_free(void* context);
void kia_protocol_decoder_v7_reset(void* context);
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration);
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context);
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
void kia_protocol_encoder_v7_free(void* context);
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_encoder_v7_stop(void* context);
LevelDuration kia_protocol_encoder_v7_yield(void* context);
+5 -2
View File
@@ -44,7 +44,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
//&subghz_protocol_honeywell,
//&subghz_protocol_legrand,
&subghz_protocol_dickert_mahs,
//&subghz_protocol_gangqi,
&subghz_protocol_gangqi,
&subghz_protocol_marantec24,
//&subghz_protocol_hollarm,
&subghz_protocol_hay21,
@@ -59,6 +59,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_vag,
&subghz_protocol_porsche_cayenne,
&subghz_protocol_ford_v0,
&ford_protocol_v1,
&subghz_protocol_psa,
&subghz_protocol_fiat_spa,
&subghz_protocol_fiat_marelli,
@@ -71,13 +72,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_kia_v3_v4,
&subghz_protocol_kia_v5,
&subghz_protocol_kia_v6,
&subghz_protocol_kia_v7,
&subghz_protocol_suzuki,
&subghz_protocol_mitsubishi_v0,
&subghz_protocol_star_line,
&subghz_protocol_scher_khan,
&subghz_protocol_sheriff_cfm,
&subghz_protocol_honda,
&subghz_protocol_chrysler,
&honda_static_protocol,
//&subghz_protocol_honda,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {
+4 -1
View File
@@ -72,11 +72,14 @@
#include "kia_v3_v4.h"
#include "kia_v5.h"
#include "kia_v6.h"
#include "kia_v7.h"
#include "suzuki.h"
#include "mitsubishi_v0.h"
#include "mazda_siemens.h"
#include "star_line.h"
#include "scher_khan.h"
#include "sheriff_cfm.h"
#include "honda.h"
#include "chrysler.h"
#include "honda_static.h"
#include "ford_v1.h"
//#include "honda_pandora.h"
+90 -86
View File
@@ -125,6 +125,10 @@ struct SubGhzProtocolDecoderPSA {
uint32_t last_key1_low;
uint32_t last_key1_high;
uint32_t te_sum;
uint16_t te_count;
uint32_t te_detected;
};
struct SubGhzProtocolEncoderPSA {
@@ -670,6 +674,9 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
instance->pattern_counter = 0;
instance->decode_count_bit = 0;
instance->mode_serialize = 0;
instance->te_sum = duration;
instance->te_count = 1;
instance->te_detected = 0;
instance->prev_duration = duration;
manchester_advance(instance->manchester_state, ManchesterEventReset,
&instance->manchester_state, NULL);
@@ -935,39 +942,35 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
return;
}
if(duration < PSA_TE_SHORT_125) {
tolerance = PSA_TE_SHORT_125 - duration;
if(tolerance < PSA_TOLERANCE_50) {
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
if(prev_diff <= PSA_TOLERANCE_49) {
instance->pattern_counter++;
} else {
instance->pattern_counter = 0;
}
instance->prev_duration = duration;
return;
// Adaptive AM preamble: accept 76-174us, average to detect actual TE
if(duration >= 76 && duration <= 174) {
if(prev_dur >= 76 && prev_dur <= 174) {
instance->pattern_counter++;
instance->te_sum += duration;
instance->te_count++;
} else {
instance->pattern_counter = 0;
instance->te_sum = duration;
instance->te_count = 1;
}
instance->prev_duration = duration;
return;
} else {
tolerance = duration - PSA_TE_SHORT_125;
if(tolerance < PSA_TOLERANCE_50) {
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
if(prev_diff <= PSA_TOLERANCE_49) {
instance->pattern_counter++;
} else {
instance->pattern_counter = 0;
}
instance->prev_duration = duration;
return;
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
new_state = PSADecoderState4;
instance->decode_data_low = 0;
instance->decode_data_high = 0;
instance->decode_count_bit = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset,
&instance->manchester_state, NULL);
instance->state = new_state;
}
// Check if this is the preamble-to-data transition (2x detected TE)
uint32_t te_avg = (instance->te_count > 0) ?
(instance->te_sum / instance->te_count) : PSA_TE_SHORT_125;
uint32_t te_long_expected = te_avg * 2;
uint32_t long_diff = psa_abs_diff(duration, te_long_expected);
if(long_diff <= te_avg && instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
instance->te_detected = te_avg;
new_state = PSADecoderState4;
instance->decode_data_low = 0;
instance->decode_data_high = 0;
instance->decode_count_bit = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset,
&instance->manchester_state, NULL);
instance->state = new_state;
instance->pattern_counter = 0;
instance->prev_duration = duration;
return;
@@ -975,67 +978,25 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
}
new_state = PSADecoderState0;
instance->pattern_counter = 0;
break;
case PSADecoderState4:
case PSADecoderState4: {
if(instance->decode_count_bit >= PSA_MAX_BITS) {
new_state = PSADecoderState0;
break;
}
if(!level) {
uint8_t manchester_input;
bool decoded_bit = false;
if(duration < PSA_TE_SHORT_125) {
tolerance = PSA_TE_SHORT_125 - duration;
if(tolerance > PSA_TOLERANCE_49) {
return;
}
manchester_input = ((level ^ 1) & 0x7f) << 1;
} else {
tolerance = duration - PSA_TE_SHORT_125;
if(tolerance < PSA_TOLERANCE_50) {
manchester_input = ((level ^ 1) & 0x7f) << 1;
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
if(level == 0) {
manchester_input = 6;
} else {
manchester_input = 4;
}
} else {
return;
}
}
if(manchester_advance(instance->manchester_state,
(ManchesterEvent)manchester_input,
&instance->manchester_state,
&decoded_bit)) {
uint32_t carry = (instance->decode_data_low >> 31) & 1;
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
instance->decode_count_bit++;
if(instance->decode_count_bit == PSA_KEY1_BITS) {
instance->key1_low = instance->decode_data_low;
instance->key1_high = instance->decode_data_high;
instance->decode_data_low = 0;
instance->decode_data_high = 0;
}
}
} else if(level) {
uint32_t end_diff;
if(duration < PSA_TE_END_500) {
end_diff = PSA_TE_END_500 - duration;
} else {
end_diff = duration - PSA_TE_END_500;
}
if(end_diff <= 99) {
if(instance->decode_count_bit != PSA_KEY2_BITS) {
return;
}
uint32_t te_s = instance->te_detected ? instance->te_detected : PSA_TE_SHORT_125;
uint32_t te_l = te_s * 2;
uint32_t te_tol = te_s / 2;
uint32_t midpoint = (te_s + te_l) / 2;
// End marker check: HIGH pulse beyond long range at 80 bits
if(level && instance->decode_count_bit == PSA_KEY2_BITS && duration > midpoint) {
uint32_t end_expected = te_s * 4;
uint32_t end_diff = psa_abs_diff(duration, end_expected);
if(end_diff <= te_s * 2) {
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
instance->key2_low = instance->decode_data_low;
instance->key2_high = instance->decode_data_high;
@@ -1052,7 +1013,6 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
instance->mode_serialize = 0x36;
}
// Only fire callback if decrypted or validation nibble matches
if(instance->decrypted != 0x50 &&
(instance->validation_field & 0xf) != 0xa) {
instance->decode_data_low = 0;
@@ -1076,12 +1036,56 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
new_state = PSADecoderState0;
instance->state = new_state;
return;
} else {
}
}
// Manchester decode: process BOTH high and low pulses (unlike original AM path)
if(duration > te_l + te_tol) {
if(duration > 10000) {
new_state = PSADecoderState0;
break;
}
return;
}
uint8_t manchester_input;
bool decoded_bit = false;
if(duration <= midpoint) {
if(psa_abs_diff(duration, te_s) > te_tol) {
return;
}
manchester_input = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(psa_abs_diff(duration, te_l) > te_tol) {
return;
}
manchester_input = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
if(instance->decode_count_bit < PSA_KEY2_BITS) {
if(manchester_advance(instance->manchester_state,
(ManchesterEvent)manchester_input,
&instance->manchester_state,
&decoded_bit)) {
uint32_t carry = (instance->decode_data_low >> 31) & 1;
// PSA AM uses inverted Manchester convention
decoded_bit = !decoded_bit;
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
instance->decode_count_bit++;
if(instance->decode_count_bit == PSA_KEY1_BITS) {
instance->key1_low = instance->decode_data_low;
instance->key1_high = instance->decode_data_high;
instance->decode_data_low = 0;
instance->decode_data_high = 0;
}
}
}
break;
}
}
instance->state = new_state;
instance->prev_duration = duration;