Compare commits

..

3 Commits

Author SHA1 Message Date
d4rks1d33
9e09b49469 Update 2026-03-25 22:03:15 -03:00
d4rks1d33
ad4cd89dc4 Update 2026-03-25 18:33:47 +00:00
d4rks1d33
692d223570 Added placeholders for Renault protocols (Valeo, Siemens, Hitag) 2026-03-25 18:24:14 +00:00
20 changed files with 1947 additions and 1100 deletions

View File

@@ -49,7 +49,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | No |
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
| Mazda | Siemens (5WK49365D) | 315/433 MHz | AM/FM | Yes | Yes | Yes |
| Kia/Hyundai | KIA/HYU V0 | 433 MHz | FM | Yes | Yes | Yes |
@@ -61,7 +61,6 @@ This project may incorporate, adapt, or build upon **other open-source projects*
| 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 |
| Honda | Honda Type A/B | 433 MHz | FM (custom) | Yes | Yes | No |
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |

View File

@@ -119,13 +119,3 @@ Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18
Custom_preset_name: FM15k
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
Custom_preset_name: Honda1
Custom_preset_module: CC1101
# G2 G3 G4 D L0 L1 L2
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
Custom_preset_name: Honda2
Custom_preset_module: CC1101
# G2 G3 G4 D L0 L1 L2
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00

View File

@@ -41,17 +41,6 @@
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
static uint8_t fiat_marelli_crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x03;
for(size_t i = 0; i < len; i++) {
crc ^= data[i];
for(uint8_t b = 0; b < 8; b++) {
crc = (crc & 0x80) ? ((crc << 1) ^ 0x01) : (crc << 1);
}
}
return crc;
}
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
.te_short = 260,
.te_long = 520,
@@ -246,10 +235,6 @@ static void fiat_marelli_encoder_rebuild_raw_data(SubGhzProtocolEncoderFiatMarel
}
instance->bit_count = instance->generic.data_count_bit;
if(instance->bit_count >= 104) {
instance->raw_data[12] = fiat_marelli_crc8(instance->raw_data, 12);
}
}
SubGhzProtocolStatus
@@ -533,24 +518,16 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
if(frame_complete) {
instance->generic.data_count_bit = instance->bit_count;
bool crc_ok = true;
if(instance->bit_count >= 104) {
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
crc_ok = (calc == instance->raw_data[12]);
}
instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) |
((uint32_t)instance->raw_data[3] << 16) |
((uint32_t)instance->raw_data[4] << 8) |
((uint32_t)instance->raw_data[5]);
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
if(crc_ok) {
instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) |
((uint32_t)instance->raw_data[3] << 16) |
((uint32_t)instance->raw_data[4] << 8) |
((uint32_t)instance->raw_data[5]);
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder_state = FiatMarelliDecoderStepReset;
@@ -651,15 +628,9 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
uint8_t fixed = instance->raw_data[7] & 0x1;
const char* crc_str = "";
if(instance->bit_count >= 104) {
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
crc_str = (calc == instance->raw_data[12]) ? " CRC:OK" : " CRC:FAIL";
}
furi_string_cat_printf(
output,
"%s %dbit%s\r\n"
"%s %dbit\r\n"
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
"Raw:%02X%02X Fixed:%X\r\n"
"Sn:%08X Cnt:%02X\r\n"
@@ -667,7 +638,6 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
"Tp:%s\r\n",
instance->generic.protocol_name,
(int)instance->bit_count,
crc_str,
instance->raw_data[8], instance->raw_data[9],
instance->raw_data[10], instance->raw_data[11],
instance->raw_data[12],

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;
}

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);

View File

@@ -75,7 +75,11 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_star_line,
&subghz_protocol_scher_khan,
&subghz_protocol_sheriff_cfm,
&subghz_protocol_honda,
&subghz_protocol_renault_hitag,
&subghz_protocol_renault_siemens,
&subghz_protocol_renault_valeo,
&subghz_protocol_renault_valeo_fsk,
&subghz_protocol_renault_marelli,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {

View File

@@ -77,4 +77,8 @@
#include "star_line.h"
#include "scher_khan.h"
#include "sheriff_cfm.h"
#include "honda.h"
#include "renault_hitag.h"
#include "renault_siemens.h"
#include "renault_valeo.h"
#include "renault_valeo_fsk.h"
#include "renault_marelli.h"

View File

@@ -758,17 +758,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;
instance->decode_data_high = 0;
instance->decode_count_bit = 0;
new_state = PSADecoderState0;
instance->state = new_state;
return;
}
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
instance->generic.data_count_bit = 64;
instance->decoder.decode_data = instance->generic.data;
@@ -1052,17 +1041,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;
instance->decode_data_high = 0;
instance->decode_count_bit = 0;
new_state = PSADecoderState0;
instance->state = new_state;
return;
}
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
instance->generic.data_count_bit = 64;
instance->decoder.decode_data = instance->generic.data;

View File

@@ -0,0 +1,14 @@
#include "renault_classifier.h"
#include <stdint.h>
// Hitag: 4063 bits
// Siemens: 6480 bits
// Valeo: 8196 bits
// Marelli: 97110 bits
RenaultProtocolType renault_classify(uint8_t bits) {
if(bits >= 40 && bits <= 63) return RenaultProtoHitag;
if(bits >= 64 && bits <= 80) return RenaultProtoSiemens;
if(bits >= 81 && bits <= 96) return RenaultProtoValeo;
if(bits >= 97 && bits <= 110) return RenaultProtoMarelli;
return RenaultProtoUnknown;
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
typedef enum {
RenaultProtoUnknown,
RenaultProtoHitag,
RenaultProtoSiemens,
RenaultProtoValeo,
RenaultProtoMarelli,
} RenaultProtocolType;
RenaultProtocolType renault_classify(uint8_t bits);

View File

@@ -0,0 +1,264 @@
#include "renault_hitag.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#define TAG "RenaultHitag"
// Hitag2 / PCF7936 keyfob — OOK PWM
// te_short ≈ 200 µs (bit 0 mark)
// te_long ≈ 400 µs (bit 1 mark)
// Space between bits ≈ te_short
// Gap between frames > 3 × te_long
#define HITAG_TE_SHORT 200
#define HITAG_TE_LONG 400
#define HITAG_TE_DELTA 120
#define HITAG_MIN_BITS 40
#define HITAG_MAX_BITS 63
#define HITAG_GAP_MIN (HITAG_TE_LONG * 3)
typedef enum {
HitagStepReset = 0,
HitagStepWaitMark,
HitagStepWaitSpace,
} HitagStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
} RenaultHitagDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t hitag_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
static void renault_hitag_extract_fields(RenaultHitagDecoder* inst) {
uint8_t total = inst->generic.data_count_bit;
if(total >= 48) {
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
inst->generic.serial = (uint32_t)((inst->generic.data >> 16) & 0x0FFFFFFF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
} else if(total >= 40) {
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
inst->generic.serial = (uint32_t)((inst->generic.data >> 12) & 0x0FFFFFF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0x0FFF);
} else {
inst->generic.btn = 0;
inst->generic.serial = (uint32_t)(inst->generic.data >> 16);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
}
}
static void renault_hitag_try_accept(RenaultHitagDecoder* inst) {
if(inst->bit_count >= HITAG_MIN_BITS && inst->bit_count <= HITAG_MAX_BITS) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_hitag_extract_fields(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_hitag_alloc(SubGhzEnvironment* env) {
UNUSED(env);
RenaultHitagDecoder* inst = malloc(sizeof(RenaultHitagDecoder));
memset(inst, 0, sizeof(RenaultHitagDecoder));
inst->base.protocol = &subghz_protocol_renault_hitag;
inst->generic.protocol_name = inst->base.protocol->name;
return inst;
}
static void renault_hitag_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_hitag_reset(void* ctx) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
inst->bit_count = 0;
inst->data = 0;
inst->parser_step = HitagStepReset;
}
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
static void renault_hitag_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
switch(inst->parser_step) {
case HitagStepReset:
if(level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
}
}
break;
case HitagStepWaitSpace:
if(!level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->parser_step = HitagStepWaitMark;
} else if(duration >= HITAG_GAP_MIN) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
} else {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
break;
case HitagStepWaitMark:
if(level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
} else {
if(duration >= HITAG_GAP_MIN) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
break;
default:
renault_hitag_reset(ctx);
break;
}
if(inst->bit_count > HITAG_MAX_BITS) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_hitag_get_hash(void* ctx) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_hitag_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
static SubGhzProtocolStatus
renault_hitag_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, HITAG_MIN_BITS);
if(ret == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
renault_hitag_extract_fields(inst);
}
return ret;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_hitag_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
renault_hitag_extract_fields(inst);
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
const SubGhzProtocolDecoder renault_hitag_decoder = {
.alloc = renault_hitag_alloc,
.free = renault_hitag_free,
.feed = renault_hitag_feed,
.reset = renault_hitag_reset,
.get_hash_data = renault_hitag_get_hash,
.serialize = renault_hitag_serialize,
.deserialize = renault_hitag_deserialize,
.get_string = renault_hitag_get_string,
};
const SubGhzProtocol subghz_protocol_renault_hitag = {
.name = SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_hitag_decoder,
.encoder = NULL,
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME "Renault_Hitag"
extern const SubGhzProtocol subghz_protocol_renault_hitag;

View File

@@ -0,0 +1,673 @@
#include "renault_marelli.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.h>
#include <furi_hal_subghz.h>
#define TAG "RenaultMarelli"
// Magneti Marelli BSI keyfob protocol (PCF7946) — Renault variant
// Found on: Renault Clio III, Modus, Kangoo II, and some Dacia models
// sharing the Fiat/Renault Marelli platform (~2004-2014)
//
// RF: 433.92 MHz, Manchester encoding (FSK modulation)
// Two timing variants with identical frame structure:
// Type A (standard): te_short ~260us, te_long ~520us
// Type B (fast/compact): te_short ~100us, te_long ~200us
// TE is auto-detected from preamble pulse averaging.
//
// Frame layout (103-104 bits = 13 bytes):
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
// Bytes 2-5: Serial (32 bits)
// Byte 6: [Button:4 | Epoch:4]
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
// Bytes 8-12: Encrypted payload (40 bits)
#define REN_MARELLI_PREAMBLE_PULSE_MIN 50
#define REN_MARELLI_PREAMBLE_PULSE_MAX 350
#define REN_MARELLI_PREAMBLE_MIN 80
#define REN_MARELLI_MAX_DATA_BITS 104
#define REN_MARELLI_MIN_DATA_BITS 80
#define REN_MARELLI_GAP_TE_MULT 4
#define REN_MARELLI_SYNC_TE_MIN_MULT 4
#define REN_MARELLI_SYNC_TE_MAX_MULT 12
#define REN_MARELLI_RETX_GAP_MIN 5000
#define REN_MARELLI_RETX_SYNC_MIN 400
#define REN_MARELLI_RETX_SYNC_MAX 2800
#define REN_MARELLI_TE_TYPE_AB_BOUNDARY 180
static const SubGhzBlockConst subghz_protocol_renault_marelli_const = {
.te_short = 260,
.te_long = 520,
.te_delta = 80,
.min_count_bit_for_found = 80,
};
struct SubGhzProtocolDecoderRenaultMarelli {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t decoder_state;
uint16_t preamble_count;
uint8_t raw_data[13];
uint8_t bit_count;
uint32_t extra_data;
uint32_t te_last;
uint32_t te_sum;
uint16_t te_count;
uint32_t te_detected;
};
struct SubGhzProtocolEncoderRenaultMarelli {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint8_t raw_data[13];
uint32_t extra_data;
uint8_t bit_count;
uint32_t te_detected;
};
typedef enum {
RenMarelliDecoderStepReset = 0,
RenMarelliDecoderStepPreamble = 1,
RenMarelliDecoderStepSync = 2,
RenMarelliDecoderStepData = 3,
RenMarelliDecoderStepRetxSync = 4,
} RenMarelliDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_renault_marelli_decoder = {
.alloc = subghz_protocol_decoder_renault_marelli_alloc,
.free = subghz_protocol_decoder_renault_marelli_free,
.feed = subghz_protocol_decoder_renault_marelli_feed,
.reset = subghz_protocol_decoder_renault_marelli_reset,
.get_hash_data = subghz_protocol_decoder_renault_marelli_get_hash_data,
.serialize = subghz_protocol_decoder_renault_marelli_serialize,
.deserialize = subghz_protocol_decoder_renault_marelli_deserialize,
.get_string = subghz_protocol_decoder_renault_marelli_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_renault_marelli_encoder = {
.alloc = subghz_protocol_encoder_renault_marelli_alloc,
.free = subghz_protocol_encoder_renault_marelli_free,
.deserialize = subghz_protocol_encoder_renault_marelli_deserialize,
.stop = subghz_protocol_encoder_renault_marelli_stop,
.yield = subghz_protocol_encoder_renault_marelli_yield,
};
const SubGhzProtocol subghz_protocol_renault_marelli = {
.name = RENAULT_MARELLI_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_renault_marelli_decoder,
.encoder = &subghz_protocol_renault_marelli_encoder,
};
// ============================================================================
// Encoder
// ============================================================================
#define REN_MARELLI_ENCODER_UPLOAD_MAX 1500
#define REN_MARELLI_ENCODER_REPEAT 3
#define REN_MARELLI_PREAMBLE_PAIRS 100
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderRenaultMarelli* instance =
calloc(1, sizeof(SubGhzProtocolEncoderRenaultMarelli));
furi_check(instance);
instance->base.protocol = &subghz_protocol_renault_marelli;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
instance->encoder.size_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
instance->encoder.upload =
malloc(REN_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_renault_marelli_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
free(instance->encoder.upload);
free(instance);
}
static bool renault_marelli_encoder_get_upload(SubGhzProtocolEncoderRenaultMarelli* instance) {
uint32_t te = instance->te_detected;
if(te == 0) te = subghz_protocol_renault_marelli_const.te_short;
uint32_t te_short = te;
uint32_t te_long = te * 2;
uint32_t gap_duration = te * 12;
uint32_t sync_duration = te * 8;
size_t index = 0;
size_t max_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
uint8_t data_bits = instance->bit_count;
if(data_bits == 0) data_bits = instance->generic.data_count_bit;
if(data_bits < REN_MARELLI_MIN_DATA_BITS || data_bits > REN_MARELLI_MAX_DATA_BITS) {
return false;
}
// Preamble: alternating short HIGH/LOW
for(uint8_t i = 0; i < REN_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
if(i < REN_MARELLI_PREAMBLE_PAIRS - 1) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
// Gap after preamble
if(index < max_upload) {
instance->encoder.upload[index++] =
level_duration_make(false, te_short + gap_duration);
}
// Sync pulse
if(index < max_upload) {
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
}
// Manchester-encode data bits
bool in_mid1 = true;
for(uint8_t bit_i = 0; bit_i < data_bits && (index + 1) < max_upload; bit_i++) {
uint8_t byte_idx = bit_i / 8;
uint8_t bit_pos = 7 - (bit_i % 8);
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
if(in_mid1) {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
in_mid1 = false;
}
} else {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
in_mid1 = true;
} else {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
}
// Trailing gap
if(in_mid1) {
if(index < max_upload) {
instance->encoder.upload[index++] =
level_duration_make(false, te_short + gap_duration * 3);
}
} else {
if(index > 0) {
instance->encoder.upload[index - 1] =
level_duration_make(false, te_short + gap_duration * 3);
}
}
instance->encoder.size_upload = index;
return index > 0;
}
static void renault_marelli_encoder_rebuild_raw_data(
SubGhzProtocolEncoderRenaultMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data));
uint64_t key = instance->generic.data;
for(int i = 0; i < 8; i++) {
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
}
uint8_t extra_bits =
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
uint8_t byte_idx = 8 + (i / 8);
uint8_t bit_pos = 7 - (i % 8);
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
instance->raw_data[byte_idx] |= (1 << bit_pos);
}
}
instance->bit_count = instance->generic.data_count_bit;
}
SubGhzProtocolStatus subghz_protocol_encoder_renault_marelli_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
uint32_t extra = 0;
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
instance->extra_data = extra;
}
uint32_t te = 0;
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
instance->te_detected = te;
}
renault_marelli_encoder_rebuild_raw_data(instance);
if(!renault_marelli_encoder_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_renault_marelli_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) {
instance->encoder.repeat--;
}
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// Decoder
// ============================================================================
static void renault_marelli_rebuild_raw_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data));
uint64_t key = instance->generic.data;
for(int i = 0; i < 8; i++) {
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
}
uint8_t extra_bits =
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
uint8_t byte_idx = 8 + (i / 8);
uint8_t bit_pos = 7 - (i % 8);
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
instance->raw_data[byte_idx] |= (1 << bit_pos);
}
}
instance->bit_count = instance->generic.data_count_bit;
if(instance->bit_count >= 56) {
instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) |
((uint32_t)instance->raw_data[3] << 16) |
((uint32_t)instance->raw_data[4] << 8) |
((uint32_t)instance->raw_data[5]);
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
}
}
static void renault_marelli_prepare_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
instance->bit_count = 0;
instance->extra_data = 0;
instance->generic.data = 0;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
instance->decoder_state = RenMarelliDecoderStepData;
}
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderRenaultMarelli* instance =
calloc(1, sizeof(SubGhzProtocolDecoderRenaultMarelli));
furi_check(instance);
instance->base.protocol = &subghz_protocol_renault_marelli;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_renault_marelli_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
free(instance);
}
void subghz_protocol_decoder_renault_marelli_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
instance->decoder_state = RenMarelliDecoderStepReset;
instance->preamble_count = 0;
instance->bit_count = 0;
instance->extra_data = 0;
instance->te_last = 0;
instance->te_sum = 0;
instance->te_count = 0;
instance->te_detected = 0;
instance->generic.data = 0;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
instance->manchester_state = ManchesterStateMid1;
}
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
uint32_t te_short = instance->te_detected
? instance->te_detected
: (uint32_t)subghz_protocol_renault_marelli_const.te_short;
uint32_t te_long = te_short * 2;
uint32_t te_delta = te_short / 2;
if(te_delta < 30) te_delta = 30;
uint32_t diff;
switch(instance->decoder_state) {
case RenMarelliDecoderStepReset:
if(level) {
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= REN_MARELLI_PREAMBLE_PULSE_MAX) {
instance->decoder_state = RenMarelliDecoderStepPreamble;
instance->preamble_count = 1;
instance->te_sum = duration;
instance->te_count = 1;
instance->te_last = duration;
}
} else {
if(duration > REN_MARELLI_RETX_GAP_MIN) {
instance->decoder_state = RenMarelliDecoderStepRetxSync;
instance->te_last = duration;
}
}
break;
case RenMarelliDecoderStepPreamble:
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= REN_MARELLI_PREAMBLE_PULSE_MAX) {
instance->preamble_count++;
instance->te_sum += duration;
instance->te_count++;
instance->te_last = duration;
} else if(!level) {
if(instance->preamble_count >= REN_MARELLI_PREAMBLE_MIN &&
instance->te_count > 0) {
instance->te_detected = instance->te_sum / instance->te_count;
uint32_t gap_threshold =
instance->te_detected * REN_MARELLI_GAP_TE_MULT;
if(duration > gap_threshold) {
instance->decoder_state = RenMarelliDecoderStepSync;
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
case RenMarelliDecoderStepSync: {
uint32_t sync_min = instance->te_detected * REN_MARELLI_SYNC_TE_MIN_MULT;
uint32_t sync_max = instance->te_detected * REN_MARELLI_SYNC_TE_MAX_MULT;
if(level && duration >= sync_min && duration <= sync_max) {
renault_marelli_prepare_data(instance);
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
}
case RenMarelliDecoderStepRetxSync:
if(level && duration >= REN_MARELLI_RETX_SYNC_MIN &&
duration <= REN_MARELLI_RETX_SYNC_MAX) {
if(!instance->te_detected) {
instance->te_detected = duration / 8;
if(instance->te_detected < 70) instance->te_detected = 100;
if(instance->te_detected > 350) instance->te_detected = 260;
}
renault_marelli_prepare_data(instance);
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
case RenMarelliDecoderStepData: {
ManchesterEvent event = ManchesterEventReset;
bool frame_complete = false;
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
diff = (duration > te_long) ? (duration - te_long) : (te_long - duration);
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
if(event != ManchesterEventReset) {
bool data_bit;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit)) {
uint32_t new_bit = data_bit ? 1 : 0;
if(instance->bit_count < REN_MARELLI_MAX_DATA_BITS) {
uint8_t byte_idx = instance->bit_count / 8;
uint8_t bit_pos = 7 - (instance->bit_count % 8);
if(new_bit) {
instance->raw_data[byte_idx] |= (1 << bit_pos);
}
}
if(instance->bit_count < 64) {
instance->generic.data = (instance->generic.data << 1) | new_bit;
} else {
instance->extra_data = (instance->extra_data << 1) | new_bit;
}
instance->bit_count++;
if(instance->bit_count >= REN_MARELLI_MAX_DATA_BITS) {
frame_complete = true;
}
}
} else {
if(instance->bit_count >= REN_MARELLI_MIN_DATA_BITS) {
frame_complete = true;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
}
if(frame_complete) {
instance->generic.data_count_bit = instance->bit_count;
instance->generic.serial =
((uint32_t)instance->raw_data[2] << 24) |
((uint32_t)instance->raw_data[3] << 16) |
((uint32_t)instance->raw_data[4] << 8) |
((uint32_t)instance->raw_data[5]);
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder_state = RenMarelliDecoderStepReset;
}
instance->te_last = duration;
break;
}
}
}
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit =
instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
};
return subghz_protocol_blocks_get_hash_data(
&decoder, (decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
uint32_t extra_bits = instance->generic.data_count_bit > 64
? (instance->generic.data_count_bit - 64)
: 0;
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
uint32_t te = instance->te_detected;
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
uint32_t extra = 0;
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
instance->extra_data = extra;
}
uint32_t te = 0;
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
instance->te_detected = te;
}
renault_marelli_rebuild_raw_data(instance);
}
return ret;
}
static const char* renault_marelli_button_name(uint8_t btn) {
switch(btn) {
case 0x1:
return "Lock";
case 0x2:
return "Unlock";
case 0x4:
return "Trunk";
case 0x7:
return "Lock";
case 0xB:
return "Unlock";
case 0xD:
return "Trunk";
default:
return "Unknown";
}
}
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
uint8_t epoch = instance->raw_data[6] & 0xF;
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
const char* variant = (instance->te_detected &&
instance->te_detected < REN_MARELLI_TE_TYPE_AB_BOUNDARY)
? "B"
: "A";
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
uint8_t fixed = instance->raw_data[7] & 0x1;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
"Raw:%02X%02X Fixed:%X\r\n"
"Sn:%08X Cnt:%02X\r\n"
"Btn:%02X:[%s] Ep:%02X\r\n"
"Tp:%s\r\n",
instance->generic.protocol_name,
(int)instance->bit_count,
instance->raw_data[8],
instance->raw_data[9],
instance->raw_data[10],
instance->raw_data[11],
instance->raw_data[12],
(unsigned)scramble,
instance->raw_data[6],
instance->raw_data[7],
(unsigned)fixed,
(unsigned int)instance->generic.serial,
(unsigned)counter,
(unsigned)instance->generic.btn,
renault_marelli_button_name(instance->generic.btn),
(unsigned)epoch,
variant);
}

View File

@@ -0,0 +1,31 @@
#pragma once
#include "base.h"
#include <flipper_format/flipper_format.h>
#define RENAULT_MARELLI_PROTOCOL_NAME "Ren_MARELLI"
typedef struct SubGhzProtocolDecoderRenaultMarelli SubGhzProtocolDecoderRenaultMarelli;
typedef struct SubGhzProtocolEncoderRenaultMarelli SubGhzProtocolEncoderRenaultMarelli;
extern const SubGhzProtocol subghz_protocol_renault_marelli;
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_renault_marelli_free(void* context);
void subghz_protocol_decoder_renault_marelli_reset(void* context);
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_renault_marelli_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_renault_marelli_stop(void* context);
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context);

View File

@@ -0,0 +1,253 @@
#include "renault_siemens.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#define TAG "RenaultSiemens"
// Siemens VDO keyfob — OOK PWM
// te_short ≈ 250 µs (bit 0 mark)
// te_long ≈ 500 µs (bit 1 mark)
// Space ≈ te_short
// Gap > 3 × te_long
#define SIEMENS_TE_SHORT 250
#define SIEMENS_TE_LONG 500
#define SIEMENS_TE_DELTA 120
#define SIEMENS_MIN_BITS 64
#define SIEMENS_MAX_BITS 80
#define SIEMENS_GAP_MIN (SIEMENS_TE_LONG * 3)
typedef enum {
SiemensStepReset = 0,
SiemensStepWaitMark,
SiemensStepWaitSpace,
} SiemensStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
} RenaultSiemensDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t siemens_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
static void renault_siemens_extract_fields(RenaultSiemensDecoder* inst) {
inst->generic.serial = (uint32_t)(inst->generic.data >> 32);
inst->generic.btn = (uint8_t)((inst->generic.data >> 28) & 0xF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
}
static void renault_siemens_try_accept(RenaultSiemensDecoder* inst) {
if(inst->bit_count >= SIEMENS_MIN_BITS && inst->bit_count <= SIEMENS_MAX_BITS) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_siemens_extract_fields(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_siemens_alloc(SubGhzEnvironment* env) {
UNUSED(env);
RenaultSiemensDecoder* inst = malloc(sizeof(RenaultSiemensDecoder));
memset(inst, 0, sizeof(RenaultSiemensDecoder));
inst->base.protocol = &subghz_protocol_renault_siemens;
inst->generic.protocol_name = inst->base.protocol->name;
return inst;
}
static void renault_siemens_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_siemens_reset(void* ctx) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
static void renault_siemens_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
switch(inst->parser_step) {
case SiemensStepReset:
if(level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
}
}
break;
case SiemensStepWaitSpace:
if(!level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->parser_step = SiemensStepWaitMark;
} else if(duration >= SIEMENS_GAP_MIN) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
} else {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
break;
case SiemensStepWaitMark:
if(level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
} else {
if(duration >= SIEMENS_GAP_MIN) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
break;
default:
renault_siemens_reset(ctx);
break;
}
if(inst->bit_count > SIEMENS_MAX_BITS) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_siemens_get_hash(void* ctx) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_siemens_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
static SubGhzProtocolStatus
renault_siemens_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, SIEMENS_MIN_BITS);
if(ret == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
renault_siemens_extract_fields(inst);
}
return ret;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_siemens_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
renault_siemens_extract_fields(inst);
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%08lX Btn:%X\r\n"
"Cnt:%04lX\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
const SubGhzProtocolDecoder renault_siemens_decoder = {
.alloc = renault_siemens_alloc,
.free = renault_siemens_free,
.feed = renault_siemens_feed,
.reset = renault_siemens_reset,
.get_hash_data = renault_siemens_get_hash,
.serialize = renault_siemens_serialize,
.deserialize = renault_siemens_deserialize,
.get_string = renault_siemens_get_string,
};
const SubGhzProtocol subghz_protocol_renault_siemens = {
.name = SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_siemens_decoder,
.encoder = NULL,
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME "Renault_Siemens"
extern const SubGhzProtocol subghz_protocol_renault_siemens;

View File

@@ -0,0 +1,334 @@
#include "renault_valeo.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#include "keeloq_common.h"
#include "../subghz_keystore.h"
#include "../subghz_keystore_i.h"
#include <m-array.h>
#define TAG "RenaultValeo"
// Valeo OOK keyfob — Captur 2017 / Clio IV / PCF7961
// OOK PWM encoding:
// te_short ≈ 66 µs → bit 0 (mark)
// te_long ≈ 264 µs → bit 1 (mark)
// Space between bits ≈ te_short (66 µs)
// Gap between frames > 500 µs
//
// Trama (64-96 bits):
// [MSB..32] fix: btn[4] + serial[28]
// [31..0] hop: 32 bits KeeLoq encrypted
#define VALEO_TE_SHORT 66
#define VALEO_TE_LONG 264
#define VALEO_TE_DELTA 60
#define VALEO_MIN_BITS 64
#define VALEO_MAX_BITS 96
#define VALEO_GAP_MIN 500
typedef enum {
ValeoStepReset = 0,
ValeoStepWaitMark,
ValeoStepWaitSpace,
} ValeoStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
SubGhzKeystore* keystore;
const char* manufacture_name;
} RenaultValeoDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t valeo_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
static void renault_valeo_decode_keeloq(RenaultValeoDecoder* inst) {
if(!inst->keystore) return;
uint32_t fix = (uint32_t)(inst->data >> 32);
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
uint8_t btn = (fix >> 28) & 0xF;
uint32_t serial = fix & 0x0FFFFFFF;
inst->generic.serial = serial;
inst->generic.btn = btn;
inst->manufacture_name = "Unknown";
for
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
// Normal Learning (Valeo primary)
if(mf->type == KEELOQ_LEARNING_NORMAL ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
// Simple Learning fallback
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_valeo_alloc(SubGhzEnvironment* env) {
RenaultValeoDecoder* inst = malloc(sizeof(RenaultValeoDecoder));
memset(inst, 0, sizeof(RenaultValeoDecoder));
inst->base.protocol = &subghz_protocol_renault_valeo;
inst->generic.protocol_name = inst->base.protocol->name;
inst->keystore = subghz_environment_get_keystore(env);
inst->manufacture_name = "Unknown";
return inst;
}
static void renault_valeo_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_valeo_reset(void* ctx) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
// ─── Feed — OOK PWM ─────────────────────────────────────────────────────────
static void renault_valeo_try_accept(RenaultValeoDecoder* inst) {
if(inst->bit_count >= VALEO_MIN_BITS && inst->bit_count <= VALEO_MAX_BITS) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_valeo_decode_keeloq(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
static void renault_valeo_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
switch(inst->parser_step) {
case ValeoStepReset:
if(level) {
if(valeo_abs_diff(duration, VALEO_TE_SHORT) < VALEO_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else if(valeo_abs_diff(duration, VALEO_TE_LONG) < VALEO_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
}
}
break;
case ValeoStepWaitSpace:
if(!level) {
if(valeo_abs_diff(duration, VALEO_TE_SHORT) < VALEO_TE_DELTA) {
inst->parser_step = ValeoStepWaitMark;
} else if(duration >= VALEO_GAP_MIN) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
} else {
// Allow some tolerance on space — accept wider spaces as inter-bit
if(duration < VALEO_GAP_MIN) {
inst->parser_step = ValeoStepWaitMark;
} else {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
}
break;
case ValeoStepWaitMark:
if(level) {
if(valeo_abs_diff(duration, VALEO_TE_SHORT) < VALEO_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else if(valeo_abs_diff(duration, VALEO_TE_LONG) < VALEO_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
} else {
if(duration >= VALEO_GAP_MIN) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
break;
default:
renault_valeo_reset(ctx);
break;
}
if(inst->bit_count > VALEO_MAX_BITS) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_valeo_get_hash(void* ctx) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_valeo_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
if(res == SubGhzProtocolStatusOk) {
if(!flipper_format_write_string_cstr(
flipper_format, "Manufacture", inst->manufacture_name)) {
res = SubGhzProtocolStatusErrorParserOthers;
}
}
return res;
}
static SubGhzProtocolStatus
renault_valeo_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, VALEO_MIN_BITS);
if(res == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
// Read manufacture name safely
FuriString* mf = furi_string_alloc();
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
// Store a copy since mf will be freed
if(furi_string_size(mf) > 0) {
inst->manufacture_name = "Loaded";
}
}
furi_string_free(mf);
// Re-extract fields
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
}
return res;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_valeo_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX Mf:%s\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt,
inst->manufacture_name);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
static const SubGhzProtocolDecoder renault_valeo_decoder = {
.alloc = renault_valeo_alloc,
.free = renault_valeo_free,
.feed = renault_valeo_feed,
.reset = renault_valeo_reset,
.get_hash_data = renault_valeo_get_hash,
.serialize = renault_valeo_serialize,
.deserialize = renault_valeo_deserialize,
.get_string = renault_valeo_get_string,
};
const SubGhzProtocol subghz_protocol_renault_valeo = {
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_valeo_decoder,
.encoder = NULL,
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME "Renault_Valeo"
extern const SubGhzProtocol subghz_protocol_renault_valeo;

View File

@@ -0,0 +1,316 @@
#include "renault_valeo_fsk.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#include "keeloq_common.h"
#include "../subghz_keystore.h"
#include "../subghz_keystore_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <m-array.h>
#define TAG "RenaultValeoFSK"
// Valeo FSK (Megane III, Scenic III, Ren3) — 2FSKDev476Async
// Manchester encoding over FSK
// te_short = 500 µs (half-bit cell)
// te_long = 1000 µs (full-bit cell)
// te_delta = 200 µs
// Preamble: alternating half-cells (min 8)
#define VALEO_FSK_TE_SHORT 500
#define VALEO_FSK_TE_LONG 1000
#define VALEO_FSK_TE_DELTA 200
#define VALEO_FSK_MIN_BITS 64
#define VALEO_FSK_MAX_BITS 96
#define VALEO_FSK_PREAMBLE_MIN 8
#ifndef DURATION_DIFF
#define DURATION_DIFF(x, y) (((x) > (y)) ? ((x) - (y)) : ((y) - (x)))
#endif
typedef enum {
ValeoFSKStepReset = 0,
ValeoFSKStepPreamble,
ValeoFSKStepDecode,
} ValeoFSKStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t preamble_count;
ManchesterState manchester_state;
SubGhzKeystore* keystore;
const char* manufacture_name;
} RenaultValeoFSKDecoder;
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
static void renault_valeo_fsk_decode_keeloq(RenaultValeoFSKDecoder* inst) {
if(!inst->keystore) return;
uint32_t fix = (uint32_t)(inst->data >> 32);
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
uint8_t btn = (fix >> 28) & 0xF;
uint32_t serial = fix & 0x0FFFFFFF;
inst->generic.serial = serial;
inst->generic.btn = btn;
inst->manufacture_name = "Unknown";
for
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
if(mf->type == KEELOQ_LEARNING_NORMAL ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
}
}
// ─── Accept helper ───────────────────────────────────────────────────────────
static void renault_valeo_fsk_try_accept(RenaultValeoFSKDecoder* inst) {
if(inst->bit_count >= VALEO_FSK_MIN_BITS &&
inst->bit_count <= VALEO_FSK_MAX_BITS) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_valeo_fsk_decode_keeloq(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_valeo_fsk_alloc(SubGhzEnvironment* env) {
RenaultValeoFSKDecoder* inst = malloc(sizeof(RenaultValeoFSKDecoder));
memset(inst, 0, sizeof(RenaultValeoFSKDecoder));
inst->base.protocol = &subghz_protocol_renault_valeo_fsk;
inst->generic.protocol_name = inst->base.protocol->name;
inst->keystore = subghz_environment_get_keystore(env);
inst->manufacture_name = "Unknown";
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepReset;
return inst;
}
static void renault_valeo_fsk_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_valeo_fsk_reset(void* ctx) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->preamble_count = 0;
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepReset;
}
// ─── Feed — Manchester over FSK ──────────────────────────────────────────────
static void renault_valeo_fsk_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
// Classify duration
ManchesterEvent event = ManchesterEventReset;
if(DURATION_DIFF(duration, VALEO_FSK_TE_SHORT) < VALEO_FSK_TE_DELTA) {
event = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
} else if(DURATION_DIFF(duration, VALEO_FSK_TE_LONG) < VALEO_FSK_TE_DELTA) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
// Out of range — gap or noise
renault_valeo_fsk_try_accept(inst);
renault_valeo_fsk_reset(ctx);
return;
}
switch(inst->decoder.parser_step) {
case ValeoFSKStepReset:
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
inst->preamble_count = 1;
inst->decoder.parser_step = ValeoFSKStepPreamble;
}
break;
case ValeoFSKStepPreamble:
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
inst->preamble_count++;
if(inst->preamble_count >= VALEO_FSK_PREAMBLE_MIN) {
inst->data = 0;
inst->bit_count = 0;
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepDecode;
}
} else {
renault_valeo_fsk_reset(ctx);
}
break;
case ValeoFSKStepDecode: {
bool bit_out = false;
ManchesterState next_state;
if(manchester_advance(
inst->manchester_state, event, &next_state, &bit_out)) {
inst->data = (inst->data << 1) | (bit_out ? 1 : 0);
inst->bit_count++;
if(inst->bit_count >= VALEO_FSK_MAX_BITS) {
renault_valeo_fsk_try_accept(inst);
renault_valeo_fsk_reset(ctx);
return;
}
}
inst->manchester_state = next_state;
break;
}
default:
renault_valeo_fsk_reset(ctx);
break;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_valeo_fsk_get_hash(void* ctx) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_valeo_fsk_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
if(res == SubGhzProtocolStatusOk) {
if(!flipper_format_write_string_cstr(
flipper_format, "Manufacture", inst->manufacture_name)) {
res = SubGhzProtocolStatusErrorParserOthers;
}
}
return res;
}
static SubGhzProtocolStatus
renault_valeo_fsk_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, VALEO_FSK_MIN_BITS);
if(res == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
FuriString* mf = furi_string_alloc();
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
if(furi_string_size(mf) > 0) {
inst->manufacture_name = "Loaded";
}
}
furi_string_free(mf);
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
}
return res;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_valeo_fsk_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX Mf:%s\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt,
inst->manufacture_name);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
static const SubGhzProtocolDecoder renault_valeo_fsk_decoder = {
.alloc = renault_valeo_fsk_alloc,
.free = renault_valeo_fsk_free,
.feed = renault_valeo_fsk_feed,
.reset = renault_valeo_fsk_reset,
.get_hash_data = renault_valeo_fsk_get_hash,
.serialize = renault_valeo_fsk_serialize,
.deserialize = renault_valeo_fsk_deserialize,
.get_string = renault_valeo_fsk_get_string,
};
const SubGhzProtocol subghz_protocol_renault_valeo_fsk = {
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_valeo_fsk_decoder,
.encoder = NULL,
};

View File

@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME "Renault_Valeo_FSK"
extern const SubGhzProtocol subghz_protocol_renault_valeo_fsk;