mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-04-08 17:05:51 +00:00
Compare commits
13 Commits
experiment
...
dev-14d10c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d10c0794 | ||
|
|
27818ccb1f | ||
|
|
0ebf26eff4 | ||
|
|
ac620e2b0e | ||
|
|
46115cdf6c | ||
|
|
f465c6edbb | ||
|
|
ad795ae7ef | ||
|
|
efff8d2f2e | ||
|
|
c9c9c74117 | ||
|
|
dc0f30dad9 | ||
|
|
38f261e23b | ||
|
|
cb1daaa4f1 | ||
|
|
b318b3e9ff |
@@ -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 | No |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
|
||||
| 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,10 +61,12 @@ 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 |
|
||||
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
||||
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
### Gate / Access Protocols
|
||||
|
||||
|
||||
@@ -119,3 +119,13 @@ 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
|
||||
|
||||
305
lib/subghz/protocols/bmw_cas4.c
Normal file
305
lib/subghz/protocols/bmw_cas4.c
Normal file
@@ -0,0 +1,305 @@
|
||||
#include "bmw_cas4.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "BmwCas4"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_bmw_cas4_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define BMW_CAS4_PREAMBLE_PULSE_MIN 300u
|
||||
#define BMW_CAS4_PREAMBLE_PULSE_MAX 700u
|
||||
#define BMW_CAS4_PREAMBLE_MIN 10u
|
||||
#define BMW_CAS4_DATA_BITS 64u
|
||||
#define BMW_CAS4_GAP_MIN 1800u
|
||||
#define BMW_CAS4_BYTE0_MARKER 0x30u
|
||||
#define BMW_CAS4_BYTE6_MARKER 0xC5u
|
||||
|
||||
struct SubGhzProtocolDecoderBmwCas4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw_data[8];
|
||||
uint8_t bit_count;
|
||||
uint32_t te_last;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderBmwCas4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
BmwCas4DecoderStepReset = 0,
|
||||
BmwCas4DecoderStepPreamble,
|
||||
BmwCas4DecoderStepData,
|
||||
} BmwCas4DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_bmw_cas4_decoder = {
|
||||
.alloc = subghz_protocol_decoder_bmw_cas4_alloc,
|
||||
.free = subghz_protocol_decoder_bmw_cas4_free,
|
||||
.feed = subghz_protocol_decoder_bmw_cas4_feed,
|
||||
.reset = subghz_protocol_decoder_bmw_cas4_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_bmw_cas4_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_bmw_cas4_serialize,
|
||||
.deserialize = subghz_protocol_decoder_bmw_cas4_deserialize,
|
||||
.get_string = subghz_protocol_decoder_bmw_cas4_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_bmw_cas4_encoder = {
|
||||
.alloc = subghz_protocol_encoder_bmw_cas4_alloc,
|
||||
.free = subghz_protocol_encoder_bmw_cas4_free,
|
||||
.deserialize = subghz_protocol_encoder_bmw_cas4_deserialize,
|
||||
.stop = subghz_protocol_encoder_bmw_cas4_stop,
|
||||
.yield = subghz_protocol_encoder_bmw_cas4_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_bmw_cas4 = {
|
||||
.name = BMW_CAS4_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_bmw_cas4_decoder,
|
||||
.encoder = &subghz_protocol_bmw_cas4_encoder,
|
||||
};
|
||||
|
||||
// Encoder stubs
|
||||
|
||||
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolEncoderBmwCas4));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.size_upload = 1;
|
||||
instance->encoder.upload = malloc(sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_bmw_cas4_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
UNUSED(context);
|
||||
UNUSED(flipper_format);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_bmw_cas4_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context) {
|
||||
UNUSED(context);
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
// Decoder
|
||||
|
||||
static void bmw_cas4_rebuild_raw_data(SubGhzProtocolDecoderBmwCas4* 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));
|
||||
}
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolDecoderBmwCas4));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
|
||||
uint32_t te_short = subghz_protocol_bmw_cas4_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_bmw_cas4_const.te_long;
|
||||
uint32_t te_delta = subghz_protocol_bmw_cas4_const.te_delta;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case BmwCas4DecoderStepReset:
|
||||
if(level && duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||
instance->decoder_state = BmwCas4DecoderStepPreamble;
|
||||
instance->preamble_count = 1;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
break;
|
||||
|
||||
case BmwCas4DecoderStepPreamble:
|
||||
if(duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||
instance->preamble_count++;
|
||||
instance->te_last = duration;
|
||||
} else if(!level && duration >= BMW_CAS4_GAP_MIN) {
|
||||
if(instance->preamble_count >= BMW_CAS4_PREAMBLE_MIN) {
|
||||
instance->bit_count = 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 = BmwCas4DecoderStepData;
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case BmwCas4DecoderStepData: {
|
||||
if(instance->bit_count >= BMW_CAS4_DATA_BITS) {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
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 < BMW_CAS4_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);
|
||||
}
|
||||
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count == BMW_CAS4_DATA_BITS) {
|
||||
if(instance->raw_data[0] == BMW_CAS4_BYTE0_MARKER &&
|
||||
instance->raw_data[6] == BMW_CAS4_BYTE6_MARKER) {
|
||||
instance->generic.data_count_bit = BMW_CAS4_DATA_BITS;
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
SubGhzBlockDecoder dec = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&dec, (dec.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
bmw_cas4_rebuild_raw_data(instance);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X %02X%02X%02X%02X%02X %02X %02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
instance->raw_data[0],
|
||||
instance->raw_data[1], instance->raw_data[2],
|
||||
instance->raw_data[3], instance->raw_data[4], instance->raw_data[5],
|
||||
instance->raw_data[6],
|
||||
instance->raw_data[7]);
|
||||
}
|
||||
31
lib/subghz/protocols/bmw_cas4.h
Normal file
31
lib/subghz/protocols/bmw_cas4.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define BMW_CAS4_PROTOCOL_NAME "BMW CAS4"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderBmwCas4 SubGhzProtocolDecoderBmwCas4;
|
||||
typedef struct SubGhzProtocolEncoderBmwCas4 SubGhzProtocolEncoderBmwCas4;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_bmw_cas4;
|
||||
|
||||
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_bmw_cas4_free(void* context);
|
||||
void subghz_protocol_decoder_bmw_cas4_reset(void* context);
|
||||
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_bmw_cas4_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_bmw_cas4_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context);
|
||||
643
lib/subghz/protocols/chrysler.c
Normal file
643
lib/subghz/protocols/chrysler.c
Normal file
@@ -0,0 +1,643 @@
|
||||
#include "chrysler.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 "Chrysler"
|
||||
|
||||
// Chrysler keyfob rolling code protocol
|
||||
// Found on: PT Cruiser, Dodge, Jeep (~2004-2010)
|
||||
//
|
||||
// RF: 433.92 MHz, OOK PWM encoding
|
||||
// Bit timing: ~4000us total period
|
||||
// Bit 0: ~300us HIGH + ~3700us LOW
|
||||
// Bit 1: ~600us HIGH + ~3400us LOW
|
||||
// Frame: 24-bit zero preamble + gap ~15600us + 80-bit data
|
||||
// Retransmission: same frame sent twice per press
|
||||
//
|
||||
// 80-bit frame layout (10 bytes):
|
||||
// Byte 0: [counter:4 | device_id:4]
|
||||
// Counter: 4-bit, bit-reversed, decrementing
|
||||
// Device ID: constant per keyfob (e.g. 0xB)
|
||||
// Bytes 1-4: nibble-interleaved rolling code + button
|
||||
// MSB(b0)=0: high nibbles = rolling, low nibbles = button
|
||||
// MSB(b0)=1: low nibbles = rolling, high nibbles = button
|
||||
// Byte 5: check byte (b1 XOR 0xC3 when MSB=0, b1 when MSB=1)
|
||||
// Byte 6: b1 XOR mask (mask depends on MSB and button)
|
||||
// Bytes 7-9: b2-b4 XOR fixed mask (redundancy copy)
|
||||
//
|
||||
// Rolling code: single 8-bit value XOR'd with per-device serial offsets
|
||||
// across all 4 byte positions. The 4 bytes are related by constant XOR
|
||||
// (the serial).
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_chrysler_const = {
|
||||
.te_short = 300,
|
||||
.te_long = 600,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
#define CHRYSLER_BIT_PERIOD 4000u
|
||||
#define CHRYSLER_BIT_TOLERANCE 800u
|
||||
#define CHRYSLER_PREAMBLE_MIN 15u
|
||||
#define CHRYSLER_PREAMBLE_GAP 10000u
|
||||
#define CHRYSLER_DATA_BITS 80u
|
||||
#define CHRYSLER_SHORT_MAX 450u
|
||||
#define CHRYSLER_LONG_MIN 450u
|
||||
#define CHRYSLER_LONG_MAX 800u
|
||||
|
||||
struct SubGhzProtocolDecoderChrysler {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw_data[10];
|
||||
uint8_t bit_count;
|
||||
uint32_t te_last;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderChrysler {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint8_t raw_data[10];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ChryslerDecoderStepReset = 0,
|
||||
ChryslerDecoderStepPreamble,
|
||||
ChryslerDecoderStepGap,
|
||||
ChryslerDecoderStepData,
|
||||
} ChryslerDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_chrysler_decoder = {
|
||||
.alloc = subghz_protocol_decoder_chrysler_alloc,
|
||||
.free = subghz_protocol_decoder_chrysler_free,
|
||||
.feed = subghz_protocol_decoder_chrysler_feed,
|
||||
.reset = subghz_protocol_decoder_chrysler_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_chrysler_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_chrysler_serialize,
|
||||
.deserialize = subghz_protocol_decoder_chrysler_deserialize,
|
||||
.get_string = subghz_protocol_decoder_chrysler_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_chrysler_encoder = {
|
||||
.alloc = subghz_protocol_encoder_chrysler_alloc,
|
||||
.free = subghz_protocol_encoder_chrysler_free,
|
||||
.deserialize = subghz_protocol_encoder_chrysler_deserialize,
|
||||
.stop = subghz_protocol_encoder_chrysler_stop,
|
||||
.yield = subghz_protocol_encoder_chrysler_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_chrysler = {
|
||||
.name = CHRYSLER_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_chrysler_decoder,
|
||||
.encoder = &subghz_protocol_chrysler_encoder,
|
||||
};
|
||||
|
||||
static uint8_t chrysler_reverse_nibble(uint8_t n) {
|
||||
return (uint8_t)(((n & 1) << 3) | ((n & 2) << 1) | ((n & 4) >> 1) | ((n & 8) >> 3));
|
||||
}
|
||||
|
||||
// Encoder
|
||||
|
||||
#define CHRYSLER_ENCODER_UPLOAD_MAX 800
|
||||
#define CHRYSLER_ENCODER_REPEAT 3
|
||||
#define CHRYSLER_PREAMBLE_BITS 24
|
||||
#define CHRYSLER_PREAMBLE_GAP_US 15600
|
||||
|
||||
static uint8_t chrysler_custom_to_btn(uint8_t custom) {
|
||||
switch(custom) {
|
||||
case 1:
|
||||
return 0x01; // Lock
|
||||
case 2:
|
||||
return 0x02; // Unlock
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void chrysler_advance_rolling(uint8_t* d) {
|
||||
// Advance the counter and rolling code for the next transmission.
|
||||
//
|
||||
// Counter: 4-bit bit-reversed in upper nibble of b0, decrementing.
|
||||
// Rolling code: nibble-interleaved into bytes 1-4, swapped based on MSB(b0).
|
||||
//
|
||||
// Step 1: Extract current rolling nibbles and button nibbles
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
uint8_t rolling[4], button[4];
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(msb == 0) {
|
||||
rolling[i] = (d[1 + i] >> 4) & 0xF;
|
||||
button[i] = d[1 + i] & 0xF;
|
||||
} else {
|
||||
rolling[i] = d[1 + i] & 0xF;
|
||||
button[i] = (d[1 + i] >> 4) & 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Decrement the bit-reversed counter
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
cnt = (cnt - 1) & 0xF;
|
||||
cnt_raw = chrysler_reverse_nibble(cnt);
|
||||
uint8_t new_msb = (cnt_raw >> 3) & 1;
|
||||
|
||||
// Step 3: Reassemble byte 0
|
||||
d[0] = (cnt_raw << 4) | (d[0] & 0x0F);
|
||||
|
||||
// Step 4: Re-interleave nibbles with new MSB
|
||||
// The rolling nibbles stay the same for one step (they change every 2 presses,
|
||||
// i.e. when MSB returns to the same value). The button nibbles may differ
|
||||
// between MSB=0 and MSB=1 states.
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(new_msb == 0) {
|
||||
d[1 + i] = (rolling[i] << 4) | (button[i] & 0xF);
|
||||
} else {
|
||||
d[1 + i] = ((button[i] & 0xF) << 4) | rolling[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void chrysler_encoder_rebuild(SubGhzProtocolEncoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
uint8_t btn = instance->generic.btn;
|
||||
|
||||
uint8_t custom = subghz_custom_btn_get();
|
||||
if(custom != 0) {
|
||||
uint8_t new_btn = chrysler_custom_to_btn(custom);
|
||||
if(new_btn != 0) btn = new_btn;
|
||||
}
|
||||
|
||||
// Determine b1^b6 mask based on button and MSB
|
||||
uint8_t b1_xor_b6;
|
||||
if(msb == 0) {
|
||||
b1_xor_b6 = (btn == 0x01) ? 0x04 : 0x08;
|
||||
} else {
|
||||
b1_xor_b6 = 0x62;
|
||||
}
|
||||
|
||||
// Rebuild byte 5
|
||||
d[5] = (msb == 0) ? (d[1] ^ 0xC3) : d[1];
|
||||
|
||||
// Rebuild byte 6
|
||||
d[6] = d[1] ^ b1_xor_b6;
|
||||
|
||||
// Rebuild bytes 7-9 from bytes 2-4
|
||||
if(msb == 0) {
|
||||
d[7] = d[2] ^ 0x63;
|
||||
d[8] = d[3] ^ 0x59;
|
||||
d[9] = d[4] ^ 0x46;
|
||||
} else {
|
||||
d[7] = d[2] ^ 0x9A;
|
||||
d[8] = d[3] ^ 0xC6;
|
||||
d[9] = d[4] ^ ((btn == 0x01) ? 0x20 : 0x10);
|
||||
}
|
||||
}
|
||||
|
||||
static bool chrysler_encoder_get_upload(SubGhzProtocolEncoderChrysler* instance) {
|
||||
uint32_t te_short = subghz_protocol_chrysler_const.te_short;
|
||||
uint32_t te_bit_period = CHRYSLER_BIT_PERIOD;
|
||||
size_t index = 0;
|
||||
size_t max_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||
|
||||
// Preamble: 24 zero bits (short HIGH + long LOW each)
|
||||
for(uint8_t i = 0; i < CHRYSLER_PREAMBLE_BITS && (index + 1) < max_upload; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, te_bit_period - te_short);
|
||||
}
|
||||
|
||||
// Gap between preamble and data
|
||||
if(index > 0) {
|
||||
instance->encoder.upload[index - 1] =
|
||||
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||
}
|
||||
|
||||
// Data: 80 bits PWM
|
||||
for(uint8_t bit_i = 0; bit_i < CHRYSLER_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;
|
||||
|
||||
uint32_t high_dur = data_bit ? 600 : te_short;
|
||||
uint32_t low_dur = te_bit_period - high_dur;
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(true, high_dur);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, low_dur);
|
||||
}
|
||||
|
||||
// Final gap after frame
|
||||
if(index > 0) {
|
||||
instance->encoder.upload[index - 1] =
|
||||
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolEncoderChrysler));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_chrysler;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||
instance->encoder.size_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||
instance->encoder.upload = malloc(CHRYSLER_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_chrysler_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
|
||||
// Rebuild raw_data from generic.data (bytes 0-7)
|
||||
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));
|
||||
}
|
||||
|
||||
// Read extra bytes 8-9
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||
instance->raw_data[9] = extra & 0xFF;
|
||||
}
|
||||
|
||||
// Advance rolling code (decrement counter, swap nibble interleaving)
|
||||
chrysler_advance_rolling(instance->raw_data);
|
||||
|
||||
// Rebuild check bytes with (possibly changed) button
|
||||
chrysler_encoder_rebuild(instance);
|
||||
|
||||
if(!chrysler_encoder_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_chrysler_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* 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;
|
||||
chrysler_advance_rolling(instance->raw_data);
|
||||
chrysler_encoder_rebuild(instance);
|
||||
chrysler_encoder_get_upload(instance);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Decoder
|
||||
|
||||
static void chrysler_parse_data(SubGhzProtocolDecoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
uint8_t dev_id = d[0] & 0xF;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
// Determine button from b1^b6 mask
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
uint8_t btn = 0;
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 == 0x04)
|
||||
btn = 0x01; // Lock
|
||||
else if(b1_xor_b6 == 0x08)
|
||||
btn = 0x02; // Unlock
|
||||
else
|
||||
btn = 0x00;
|
||||
} else {
|
||||
btn = 0xFF; // Can't distinguish from MSB=1 mask (both = 0x62)
|
||||
}
|
||||
|
||||
// Serial: XOR offsets between byte positions (constant per device)
|
||||
// We derive it from the relationship between byte positions
|
||||
// serial_bytes[i] = rolling_value XOR bytes[1+i]_rolling_nibble
|
||||
// Since all positions share the same LFSR, XOR between positions is the serial
|
||||
|
||||
instance->generic.serial =
|
||||
((uint32_t)(d[1] ^ d[2]) << 24) |
|
||||
((uint32_t)(d[1] ^ d[3]) << 16) |
|
||||
((uint32_t)(d[1] ^ d[4]) << 8) |
|
||||
((uint32_t)dev_id);
|
||||
|
||||
instance->generic.cnt = cnt;
|
||||
instance->generic.btn = (btn != 0xFF) ? btn : 0;
|
||||
|
||||
// Store full 80-bit data
|
||||
instance->generic.data =
|
||||
((uint64_t)d[0] << 56) | ((uint64_t)d[1] << 48) |
|
||||
((uint64_t)d[2] << 40) | ((uint64_t)d[3] << 32) |
|
||||
((uint64_t)d[4] << 24) | ((uint64_t)d[5] << 16) |
|
||||
((uint64_t)d[6] << 8) | ((uint64_t)d[7]);
|
||||
instance->generic.data_count_bit = CHRYSLER_DATA_BITS;
|
||||
}
|
||||
|
||||
static bool chrysler_validate(SubGhzProtocolDecoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
// Check byte 5: should be b1 XOR 0xC3 (MSB=0) or b1 (MSB=1)
|
||||
if(msb == 0) {
|
||||
if(d[5] != (d[1] ^ 0xC3)) return false;
|
||||
} else {
|
||||
if(d[5] != d[1]) return false;
|
||||
}
|
||||
|
||||
// Check bytes 6-9 vs 1-4 XOR mask consistency
|
||||
// b1^b6 should be a known mask
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 != 0x04 && b1_xor_b6 != 0x08) return false;
|
||||
} else {
|
||||
if(b1_xor_b6 != 0x62) return false;
|
||||
}
|
||||
|
||||
// Check bytes 2-4 vs 7-9 XOR mask is consistent
|
||||
// The XOR mask for bytes 2-4 vs 7-9 should be the same across all 3 pairs
|
||||
uint8_t mask2 = d[2] ^ d[7];
|
||||
uint8_t mask3 = d[3] ^ d[8];
|
||||
uint8_t mask4 = d[4] ^ d[9];
|
||||
|
||||
// Masks should be one of the known patterns
|
||||
if(msb == 0) {
|
||||
if(mask2 != 0x63 || mask3 != 0x59 || mask4 != 0x46) return false;
|
||||
} else {
|
||||
// MSB=1 masks: 9A C6 20 or 9A C6 10
|
||||
if(mask2 != 0x9A || mask3 != 0xC6) return false;
|
||||
if(mask4 != 0x20 && mask4 != 0x10) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void chrysler_rebuild_raw_data(SubGhzProtocolDecoderChrysler* 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));
|
||||
}
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolDecoderChrysler));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_chrysler;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case ChryslerDecoderStepReset:
|
||||
if(level && duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||
instance->te_last = duration;
|
||||
instance->decoder_state = ChryslerDecoderStepPreamble;
|
||||
instance->preamble_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepPreamble:
|
||||
if(!level) {
|
||||
uint32_t total = instance->te_last + duration;
|
||||
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE &&
|
||||
instance->te_last <= CHRYSLER_SHORT_MAX) {
|
||||
instance->preamble_count++;
|
||||
} else if(duration > CHRYSLER_PREAMBLE_GAP &&
|
||||
instance->preamble_count >= CHRYSLER_PREAMBLE_MIN) {
|
||||
instance->decoder_state = ChryslerDecoderStepGap;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepGap:
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->decoder_state = ChryslerDecoderStepData;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepData:
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
uint32_t total = instance->te_last + duration;
|
||||
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE) {
|
||||
bool bit_val = (instance->te_last >= CHRYSLER_LONG_MIN);
|
||||
|
||||
if(instance->bit_count < CHRYSLER_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(bit_val) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
if(instance->bit_count == CHRYSLER_DATA_BITS) {
|
||||
if(chrysler_validate(instance)) {
|
||||
chrysler_parse_data(instance);
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(instance->bit_count >= CHRYSLER_DATA_BITS) {
|
||||
if(chrysler_validate(instance)) {
|
||||
chrysler_parse_data(instance);
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
SubGhzBlockDecoder dec = {
|
||||
.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(&dec, (dec.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t extra = ((uint32_t)instance->raw_data[8] << 8) | instance->raw_data[9];
|
||||
flipper_format_write_uint32(flipper_format, "Extra", &extra, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
chrysler_rebuild_raw_data(instance);
|
||||
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||
instance->raw_data[9] = extra & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* chrysler_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
uint8_t dev_id = d[0] & 0xF;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
uint8_t btn = instance->generic.btn;
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 == 0x04)
|
||||
btn = 0x01;
|
||||
else if(b1_xor_b6 == 0x08)
|
||||
btn = 0x02;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X%02X%02X%02X%02X %02X%02X%02X%02X%02X\r\n"
|
||||
"Cnt:%X Btn:%s Dev:%X\r\n"
|
||||
"Sn:%08lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
d[0], d[1], d[2], d[3], d[4],
|
||||
d[5], d[6], d[7], d[8], d[9],
|
||||
(unsigned)cnt,
|
||||
chrysler_button_name(btn),
|
||||
(unsigned)dev_id,
|
||||
(unsigned long)instance->generic.serial);
|
||||
}
|
||||
31
lib/subghz/protocols/chrysler.h
Normal file
31
lib/subghz/protocols/chrysler.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define CHRYSLER_PROTOCOL_NAME "Chrysler"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderChrysler SubGhzProtocolDecoderChrysler;
|
||||
typedef struct SubGhzProtocolEncoderChrysler SubGhzProtocolEncoderChrysler;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_chrysler;
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_chrysler_free(void* context);
|
||||
void subghz_protocol_decoder_chrysler_reset(void* context);
|
||||
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_chrysler_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_chrysler_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context);
|
||||
@@ -41,6 +41,17 @@
|
||||
#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,
|
||||
@@ -235,6 +246,10 @@ 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
|
||||
@@ -518,16 +533,24 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
||||
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;
|
||||
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]);
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||
@@ -628,9 +651,15 @@ 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\r\n"
|
||||
"%s %dbit%s\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"
|
||||
@@ -638,6 +667,7 @@ 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],
|
||||
|
||||
1082
lib/subghz/protocols/honda.c
Normal file
1082
lib/subghz/protocols/honda.c
Normal file
File diff suppressed because it is too large
Load Diff
198
lib/subghz/protocols/honda.h
Normal file
198
lib/subghz/protocols/honda.h
Normal file
@@ -0,0 +1,198 @@
|
||||
#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>
|
||||
|
||||
/* ── Protocol name ─────────────────────────────────────────────────────── */
|
||||
#define SUBGHZ_PROTOCOL_HONDA_NAME "Honda"
|
||||
|
||||
#define HONDA_TE_SHORT 250u
|
||||
#define HONDA_TE_LONG 480u
|
||||
#define HONDA_TE_DELTA 62u
|
||||
|
||||
/* ── FSK adaptive decoder parameters ──────────────────────────────────── */
|
||||
|
||||
#define HONDA_FSK_DUR_MIN_US 35u
|
||||
#define HONDA_FSK_DUR_MAX_US 2000u
|
||||
|
||||
//#define HONDA_FSK_GAP_US 3000u
|
||||
#define HONDA_FSK_GAP_US 400u
|
||||
|
||||
#define HONDA_FSK_COLLECT_N 32u
|
||||
#define HONDA_FSK_TOL_PCT 40u
|
||||
|
||||
#define HONDA_RAW_EDGE_BUF 512u
|
||||
#define HONDA_MAN_BIT_BUF 256u
|
||||
|
||||
#define HONDA_FSK_MIN_PREAMBLE_BITS 16u
|
||||
|
||||
#define HONDA_FRAME_BITS_M1 88u
|
||||
#define HONDA_FRAME_BITS_M2 66u
|
||||
#define HONDA_MIN_BITS HONDA_FRAME_BITS_M2
|
||||
|
||||
/* ── Mode 2 OOK parameters ────────────────────── */
|
||||
#define HONDA_TE_M2 400u
|
||||
#define HONDA_TE_M2_DELTA 100u
|
||||
#define HONDA_MIN_PREAMBLE_PULSES_M1 280u
|
||||
#define HONDA_MIN_PREAMBLE_PULSES_M2 18u
|
||||
#define HONDA_PREAMBLE_CYCLES_M1 312u
|
||||
#define HONDA_PREAMBLE_CYCLES_M2 23u
|
||||
#define HONDA_GAP_M1_MIN_US 500u
|
||||
#define HONDA_GAP_M1_MAX_US 1100u
|
||||
#define HONDA_GAP_M2_MIN_US 3000u
|
||||
#define HONDA_GAP_M2_MAX_US 5500u
|
||||
#define HONDA_GUARD_TIME_US 1000u
|
||||
|
||||
/* ── Button codes ──────────────────────────────────────────────────────── */
|
||||
#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_CUSTOM_BTN_MAX 5u
|
||||
|
||||
/* ── Lookup tables ─────────────────────────────────────────── */
|
||||
#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}
|
||||
|
||||
/* ── CC1101 FSK preset ──────────────────────────────────────────────────── */
|
||||
#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
|
||||
|
||||
/* ── External declarations ──────────────────────────────────────────────── */
|
||||
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);
|
||||
@@ -44,7 +44,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
//&subghz_protocol_honeywell,
|
||||
//&subghz_protocol_legrand,
|
||||
&subghz_protocol_dickert_mahs,
|
||||
//&subghz_protocol_gangqi,
|
||||
&subghz_protocol_gangqi,
|
||||
&subghz_protocol_marantec24,
|
||||
//&subghz_protocol_hollarm,
|
||||
&subghz_protocol_hay21,
|
||||
@@ -62,6 +62,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_psa,
|
||||
&subghz_protocol_fiat_spa,
|
||||
&subghz_protocol_fiat_marelli,
|
||||
// &subghz_protocol_bmw_cas4,
|
||||
&subghz_protocol_subaru,
|
||||
&subghz_protocol_mazda_siemens,
|
||||
&subghz_protocol_kia_v0,
|
||||
@@ -75,11 +76,8 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_star_line,
|
||||
&subghz_protocol_scher_khan,
|
||||
&subghz_protocol_sheriff_cfm,
|
||||
&subghz_protocol_renault_hitag,
|
||||
&subghz_protocol_renault_siemens,
|
||||
&subghz_protocol_renault_valeo,
|
||||
&subghz_protocol_renault_valeo_fsk,
|
||||
&subghz_protocol_renault_marelli,
|
||||
// until fix &subghz_protocol_honda,
|
||||
&subghz_protocol_chrysler,
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "psa.h"
|
||||
#include "fiat_spa.h"
|
||||
#include "fiat_marelli.h"
|
||||
#include "bmw_cas4.h"
|
||||
#include "subaru.h"
|
||||
#include "kia_generic.h"
|
||||
#include "kia_v0.h"
|
||||
@@ -77,8 +78,5 @@
|
||||
#include "star_line.h"
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
#include "renault_hitag.h"
|
||||
#include "renault_siemens.h"
|
||||
#include "renault_valeo.h"
|
||||
#include "renault_valeo_fsk.h"
|
||||
#include "renault_marelli.h"
|
||||
#include "honda.h"
|
||||
#include "chrysler.h"
|
||||
|
||||
@@ -758,6 +758,17 @@ 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;
|
||||
@@ -1041,6 +1052,17 @@ 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;
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "renault_classifier.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Hitag: 40–63 bits
|
||||
// Siemens: 64–80 bits
|
||||
// Valeo: 81–96 bits
|
||||
// Marelli: 97–110 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;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
RenaultProtoUnknown,
|
||||
RenaultProtoHitag,
|
||||
RenaultProtoSiemens,
|
||||
RenaultProtoValeo,
|
||||
RenaultProtoMarelli,
|
||||
} RenaultProtocolType;
|
||||
|
||||
RenaultProtocolType renault_classify(uint8_t bits);
|
||||
@@ -1,264 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME "Renault_Hitag"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_hitag;
|
||||
@@ -1,673 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#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);
|
||||
@@ -1,253 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME "Renault_Siemens"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_siemens;
|
||||
@@ -1,334 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME "Renault_Valeo"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_valeo;
|
||||
@@ -1,316 +0,0 @@
|
||||
#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,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#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;
|
||||
Reference in New Issue
Block a user