mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-03-30 16:45:38 +00:00
Compare commits
4 Commits
dev-e445b2
...
dev-8bf12d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bf12df45d | ||
|
|
f3d08573a1 | ||
|
|
9e52a6eb6b | ||
|
|
faf669b457 |
@@ -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 |
|
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||||
| Fiat | Fiat Marelli | 433 MHz | AM | No | Yes | No |
|
| Fiat | Fiat Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
|
| Mazda | Siemens (5WK49365D) | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||||
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
|
| Kia/Hyundai | Kia V0 | 433 MHz | FM | Yes | Yes | Yes |
|
||||||
|
|||||||
296
lib/subghz/protocols/bmw.c
Normal file
296
lib/subghz/protocols/bmw.c
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
#include "bmw.h"
|
||||||
|
|
||||||
|
#define TAG "SubGhzProtocolBMW_868"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_bmw_const = {
|
||||||
|
.te_short = 350, // BMW 868 MHz
|
||||||
|
.te_long = 700, // ~2 × te_short
|
||||||
|
.te_delta = 120,
|
||||||
|
.min_count_bit_for_found = 61,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderBMW {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint16_t header_count;
|
||||||
|
uint8_t crc_type; // 0 = unknown, 8 = CRC8, 16 = CRC16
|
||||||
|
} SubGhzProtocolDecoderBMW;
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolEncoderBMW {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
} SubGhzProtocolEncoderBMW;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
BMWDecoderStepReset = 0,
|
||||||
|
BMWDecoderStepCheckPreambula,
|
||||||
|
BMWDecoderStepSaveDuration,
|
||||||
|
BMWDecoderStepCheckDuration,
|
||||||
|
} BMWDecoderStep;
|
||||||
|
|
||||||
|
static void subghz_protocol_decoder_bmw_reset_internal(SubGhzProtocolDecoderBMW* instance) {
|
||||||
|
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||||
|
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->crc_type = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_bmw_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_bmw_alloc,
|
||||||
|
.free = subghz_protocol_decoder_bmw_free,
|
||||||
|
|
||||||
|
.feed = subghz_protocol_decoder_bmw_feed,
|
||||||
|
.reset = subghz_protocol_decoder_bmw_reset,
|
||||||
|
|
||||||
|
.get_hash_data = subghz_protocol_decoder_bmw_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_bmw_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_bmw_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_bmw_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_bmw_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_bmw = {
|
||||||
|
.name = BMW_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &subghz_protocol_bmw_decoder,
|
||||||
|
.encoder = &subghz_protocol_bmw_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------- Allocation / Reset / Free -------------------
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_bmw_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = calloc(1, sizeof(SubGhzProtocolDecoderBMW));
|
||||||
|
instance->base.protocol = &subghz_protocol_bmw;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
subghz_protocol_decoder_bmw_reset(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
subghz_protocol_decoder_bmw_reset_internal(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- CRC -------------------
|
||||||
|
// BMW utilise CRC-8 (poly 0x31, init 0x00)
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_bmw_crc8(uint8_t* data, size_t len) {
|
||||||
|
uint8_t crc = 0x00;
|
||||||
|
for(size_t i = 0; i < len; i++) {
|
||||||
|
crc ^= data[i];
|
||||||
|
for(uint8_t j = 0; j < 8; j++) {
|
||||||
|
if(crc & 0x80)
|
||||||
|
crc = (uint8_t)((crc << 1) ^ 0x31);
|
||||||
|
else
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BMW utilise aussi CRC-16 (poly 0x1021, init 0xFFFF)
|
||||||
|
uint16_t subghz_protocol_bmw_crc16(uint8_t* data, size_t len) {
|
||||||
|
uint16_t crc = 0xFFFF;
|
||||||
|
for(size_t i = 0; i < len; i++) {
|
||||||
|
crc ^= ((uint16_t)data[i] << 8);
|
||||||
|
for(uint8_t j = 0; j < 8; j++) {
|
||||||
|
if(crc & 0x8000)
|
||||||
|
crc = (crc << 1) ^ 0x1021;
|
||||||
|
else
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Decoder Feed -------------------
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case BMWDecoderStepReset:
|
||||||
|
if(level && (DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepCheckPreambula;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BMWDecoderStepCheckPreambula:
|
||||||
|
if(level) {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta) ||
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
instance->header_count++;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||||
|
subghz_protocol_bmw_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_long) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
if(instance->header_count > 15) {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0ULL;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BMWDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
if(duration >=
|
||||||
|
(subghz_protocol_bmw_const.te_long + subghz_protocol_bmw_const.te_delta * 2UL)) {
|
||||||
|
if(instance->decoder.decode_count_bit >=
|
||||||
|
subghz_protocol_bmw_const.min_count_bit_for_found) {
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
|
||||||
|
// Perform CRC check with both CRC8 and CRC16
|
||||||
|
uint8_t* raw_bytes = (uint8_t*)&instance->generic.data;
|
||||||
|
size_t raw_len = (instance->generic.data_count_bit + 7) / 8;
|
||||||
|
uint8_t crc8 = subghz_protocol_bmw_crc8(raw_bytes, raw_len - 1);
|
||||||
|
if(crc8 == raw_bytes[raw_len - 1]) {
|
||||||
|
instance->crc_type = 8;
|
||||||
|
} else {
|
||||||
|
uint16_t crc16 = subghz_protocol_bmw_crc16(raw_bytes, raw_len - 2);
|
||||||
|
uint16_t rx_crc16 = (raw_bytes[raw_len - 2] << 8) | raw_bytes[raw_len - 1];
|
||||||
|
if(crc16 == rx_crc16) {
|
||||||
|
instance->crc_type = 16;
|
||||||
|
} else {
|
||||||
|
instance->crc_type = 0; // invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(instance->crc_type != 0 && instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subghz_protocol_decoder_bmw_reset_internal(instance);
|
||||||
|
} else {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepCheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BMWDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_short) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_bmw_const.te_long) <
|
||||||
|
subghz_protocol_bmw_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_bmw_const.te_long) <
|
||||||
|
subghz_protocol_bmw_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = BMWDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Utils -------------------
|
||||||
|
|
||||||
|
static void subghz_protocol_bmw_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||||
|
instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF);
|
||||||
|
instance->btn = (instance->data >> 8) & 0x0F;
|
||||||
|
instance->cnt = (instance->data >> 40) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- API -------------------
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_bmw_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_bmw_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_bmw_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
return subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic, flipper_format, subghz_protocol_bmw_const.min_count_bit_for_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_bmw_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderBMW* instance = context;
|
||||||
|
|
||||||
|
subghz_protocol_bmw_check_remote_controller(&instance->generic);
|
||||||
|
uint32_t hi = instance->generic.data >> 32;
|
||||||
|
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit (CRC:%d)\r\n"
|
||||||
|
"Key:%08lX%08lX\r\n"
|
||||||
|
"Sn:%07lX Btn:%X Cnt:%04lX\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
instance->crc_type,
|
||||||
|
hi,
|
||||||
|
lo,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
instance->generic.cnt);
|
||||||
|
}
|
||||||
29
lib/subghz/protocols/bmw.h
Normal file
29
lib/subghz/protocols/bmw.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/types.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
#include <flipper_format/flipper_format.h>
|
||||||
|
#include <lib/toolbox/manchester_decoder.h>
|
||||||
|
|
||||||
|
#define BMW_PROTOCOL_NAME "BMW"
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_bmw;
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_bmw_alloc(SubGhzEnvironment* environment);
|
||||||
|
void subghz_protocol_decoder_bmw_free(void* context);
|
||||||
|
void subghz_protocol_decoder_bmw_reset(void* context);
|
||||||
|
void subghz_protocol_decoder_bmw_feed(void* context, bool level, uint32_t duration);
|
||||||
|
uint8_t subghz_protocol_decoder_bmw_get_hash_data(void* context);
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_bmw_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
SubGhzProtocolStatus
|
||||||
|
subghz_protocol_decoder_bmw_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
|
void subghz_protocol_decoder_bmw_get_string(void* context, FuriString* output);
|
||||||
281
lib/subghz/protocols/citroen.c
Normal file
281
lib/subghz/protocols/citroen.c
Normal file
@@ -0,0 +1,281 @@
|
|||||||
|
#include "citroen.h"
|
||||||
|
|
||||||
|
#define TAG "SubGhzProtocolCitroen"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_citroen_const = {
|
||||||
|
.te_short = 370, // Short pulse duration
|
||||||
|
.te_long = 772, // Long pulse duration
|
||||||
|
.te_delta = 152, // Tolerance
|
||||||
|
.min_count_bit_for_found = 66,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderCitroen {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint16_t header_count;
|
||||||
|
uint8_t packet_count;
|
||||||
|
} SubGhzProtocolDecoderCitroen;
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolEncoderCitroen {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
} SubGhzProtocolEncoderCitroen;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
CitroenDecoderStepReset = 0,
|
||||||
|
CitroenDecoderStepCheckPreamble,
|
||||||
|
CitroenDecoderStepSaveDuration,
|
||||||
|
CitroenDecoderStepCheckDuration,
|
||||||
|
} CitroenDecoderStep;
|
||||||
|
|
||||||
|
static void subghz_protocol_decoder_citroen_reset_internal(SubGhzProtocolDecoderCitroen* instance) {
|
||||||
|
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||||
|
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->packet_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_citroen_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_citroen_alloc,
|
||||||
|
.free = subghz_protocol_decoder_citroen_free,
|
||||||
|
|
||||||
|
.feed = subghz_protocol_decoder_citroen_feed,
|
||||||
|
.reset = subghz_protocol_decoder_citroen_reset,
|
||||||
|
|
||||||
|
.get_hash_data = subghz_protocol_decoder_citroen_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_citroen_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_citroen_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_citroen_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_citroen_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_citroen = {
|
||||||
|
.name = CITROEN_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &subghz_protocol_citroen_decoder,
|
||||||
|
.encoder = &subghz_protocol_citroen_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------- Allocation / Reset / Free -------------------
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_citroen_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = calloc(1, sizeof(SubGhzProtocolDecoderCitroen));
|
||||||
|
instance->base.protocol = &subghz_protocol_citroen;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
subghz_protocol_decoder_citroen_reset(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_citroen_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_citroen_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
subghz_protocol_decoder_citroen_reset_internal(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Helper Functions -------------------
|
||||||
|
|
||||||
|
static uint8_t reverse8(uint8_t byte) {
|
||||||
|
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||||
|
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||||
|
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Citroën/PSA data structure
|
||||||
|
static bool subghz_protocol_citroen_parse_data(SubGhzProtocolDecoderCitroen* instance) {
|
||||||
|
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||||
|
|
||||||
|
// PSA structure (similar to Peugeot Keeloq)
|
||||||
|
// Check preamble
|
||||||
|
if(b[0] != 0xFF || (b[1] & 0xF0) != 0xF0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract encrypted part (32 bits) - reversed
|
||||||
|
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) |
|
||||||
|
(reverse8(b[2]) << 16) |
|
||||||
|
(reverse8(b[1] & 0x0F) << 8) |
|
||||||
|
reverse8(b[0]);
|
||||||
|
|
||||||
|
// Extract serial number (28 bits) - reversed
|
||||||
|
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 20) |
|
||||||
|
(reverse8(b[6]) << 12) |
|
||||||
|
(reverse8(b[5]) << 4) |
|
||||||
|
(reverse8(b[4]) >> 4);
|
||||||
|
|
||||||
|
// Extract button bits (4 bits)
|
||||||
|
uint8_t button_bits = (encrypted >> 28) & 0x0F;
|
||||||
|
|
||||||
|
// Store parsed data
|
||||||
|
instance->generic.serial = serial;
|
||||||
|
instance->generic.btn = button_bits;
|
||||||
|
instance->generic.cnt = (encrypted >> 16) & 0xFFFF; // Counter
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Decoder Feed -------------------
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_citroen_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case CitroenDecoderStepReset:
|
||||||
|
if(level && (DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta)) {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepCheckPreamble;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CitroenDecoderStepCheckPreamble:
|
||||||
|
if(level) {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta)) {
|
||||||
|
instance->header_count++;
|
||||||
|
} else if((DURATION_DIFF(duration, 4400) < 500) && instance->header_count >= 10) {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0ULL;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CitroenDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
if(duration >= (subghz_protocol_citroen_const.te_long * 3)) {
|
||||||
|
if(instance->decoder.decode_count_bit >=
|
||||||
|
subghz_protocol_citroen_const.min_count_bit_for_found) {
|
||||||
|
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
|
||||||
|
if(subghz_protocol_citroen_parse_data(instance)) {
|
||||||
|
instance->packet_count++;
|
||||||
|
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subghz_protocol_decoder_citroen_reset_internal(instance);
|
||||||
|
} else {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepCheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CitroenDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
// PWM decoding
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_citroen_const.te_long) <
|
||||||
|
subghz_protocol_citroen_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_citroen_const.te_long) <
|
||||||
|
subghz_protocol_citroen_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_citroen_const.te_short) <
|
||||||
|
subghz_protocol_citroen_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = CitroenDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- API -------------------
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_citroen_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_citroen_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_citroen_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
return subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic,
|
||||||
|
flipper_format,
|
||||||
|
subghz_protocol_citroen_const.min_count_bit_for_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_citroen_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderCitroen* instance = context;
|
||||||
|
|
||||||
|
uint32_t hi = instance->generic.data >> 32;
|
||||||
|
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Key:%08lX%08lX\r\n"
|
||||||
|
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
|
||||||
|
"Type:PSA/Keeloq\r\n"
|
||||||
|
"Models:2005-2018\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
hi,
|
||||||
|
lo,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
instance->generic.cnt);
|
||||||
|
}
|
||||||
77
lib/subghz/protocols/citroen.h
Normal file
77
lib/subghz/protocols/citroen.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
|
||||||
|
#define CITROEN_PROTOCOL_NAME "Citroen"
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_citroen;
|
||||||
|
extern const SubGhzProtocolDecoder subghz_protocol_citroen_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_citroen_encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates memory for the Citroën protocol decoder.
|
||||||
|
* @param environment Pointer to SubGhzEnvironment
|
||||||
|
* @return Pointer to the allocated decoder instance
|
||||||
|
*/
|
||||||
|
void* subghz_protocol_decoder_citroen_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory used by the Citroën protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_citroen_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Citroën protocol decoder state.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_citroen_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feeds a pulse/gap into the Citroën protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param level Signal level (true = high, false = low)
|
||||||
|
* @param duration Duration of the level in microseconds
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_citroen_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hash of the decoded Citroën data.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @return Hash byte
|
||||||
|
*/
|
||||||
|
uint8_t subghz_protocol_decoder_citroen_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the decoded Citroën data into a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @param preset Pointer to the radio preset
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_citroen_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes Citroën data from a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_citroen_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the decoded Citroën data into a human-readable string.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param output Pointer to the FuriString output buffer
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_citroen_get_string(void* context, FuriString* output);
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
#define TAG "FiatMarelli"
|
#define TAG "FiatMarelli"
|
||||||
|
|
||||||
// Magneti Marelli BSI keyfob protocol
|
// Magneti Marelli BSI keyfob protocol (PCF7946)
|
||||||
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
||||||
//
|
//
|
||||||
// RF: 433.92 MHz, Manchester encoding
|
// RF: 433.92 MHz, Manchester encoding
|
||||||
@@ -13,44 +13,25 @@
|
|||||||
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
||||||
// TE is auto-detected from preamble pulse averaging.
|
// TE is auto-detected from preamble pulse averaging.
|
||||||
//
|
//
|
||||||
// Preamble: many short-short pairs (alternating TE HIGH/LOW)
|
|
||||||
// Gap: ~12x TE LOW
|
|
||||||
// Sync: ~8x TE HIGH
|
|
||||||
// Data: 103-104 Manchester bits (13 bytes), first 14-16 bits are 0xFFF preamble residue
|
|
||||||
// Retransmissions: 7-10 per press
|
|
||||||
//
|
|
||||||
// Frame layout (103-104 bits = 13 bytes):
|
// Frame layout (103-104 bits = 13 bytes):
|
||||||
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||||
// Bytes 2-5: Fixed ID / Serial (32 bits)
|
// Bytes 2-5: Serial (32 bits)
|
||||||
// Byte 6: [Button:4 | Epoch:4]
|
// Byte 6: [Button:4 | Epoch:4]
|
||||||
// Button (upper nibble): 0x7=Lock, 0xB=Unlock, 0xD=Trunk
|
|
||||||
// Epoch (lower nibble): 4-bit counter extension (decrements on counter wrap)
|
|
||||||
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||||
// Counter: 5-bit plaintext decrementing counter (MSBs of byte)
|
|
||||||
// Scramble: 2 bits dependent on counter/button/epoch
|
|
||||||
// LSB: fixed (1 for Type A, 0 for Type B)
|
|
||||||
// Bytes 8-12: Encrypted payload (40 bits)
|
// Bytes 8-12: Encrypted payload (40 bits)
|
||||||
// Fixed bits: bit 37=0, bit 38=1, bit 47=0 (relative to rolling code)
|
|
||||||
//
|
|
||||||
// Full counter: 52 bits = (Epoch << 48) | Rolling_48bit (shared across all buttons)
|
|
||||||
// Cipher: proprietary, ~38 effective encrypted bits, weak MSB diffusion
|
|
||||||
|
|
||||||
// Preamble: accept short pulses in this range for auto-TE detection
|
|
||||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50
|
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50
|
||||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350
|
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350
|
||||||
#define FIAT_MARELLI_PREAMBLE_MIN 80 // Min preamble pulses before gap detection
|
#define FIAT_MARELLI_PREAMBLE_MIN 80
|
||||||
#define FIAT_MARELLI_MAX_DATA_BITS 104 // Max data bits to collect (13 bytes)
|
#define FIAT_MARELLI_MAX_DATA_BITS 104
|
||||||
#define FIAT_MARELLI_MIN_DATA_BITS 80 // Min bits for a valid frame
|
#define FIAT_MARELLI_MIN_DATA_BITS 80
|
||||||
// Gap/sync relative multipliers (applied to auto-detected te_short)
|
#define FIAT_MARELLI_GAP_TE_MULT 4
|
||||||
#define FIAT_MARELLI_GAP_TE_MULT 4 // Gap > 4 * te_short
|
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4
|
||||||
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4 // Sync >= 4 * te_short
|
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12
|
||||||
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12 // Sync <= 12 * te_short
|
#define FIAT_MARELLI_RETX_GAP_MIN 5000
|
||||||
// Fallback for retransmission detection (no preamble)
|
#define FIAT_MARELLI_RETX_SYNC_MIN 400
|
||||||
#define FIAT_MARELLI_RETX_GAP_MIN 5000 // Direct gap detection from Reset (us)
|
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
|
||||||
#define FIAT_MARELLI_RETX_SYNC_MIN 400 // Retx sync min (us)
|
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||||
#define FIAT_MARELLI_RETX_SYNC_MAX 2800 // Retx sync max (us)
|
|
||||||
// TE boundary for variant classification
|
|
||||||
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180 // < 180 = Type B, >= 180 = Type A
|
|
||||||
|
|
||||||
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
||||||
.te_short = 260,
|
.te_short = 260,
|
||||||
@@ -66,20 +47,23 @@ struct SubGhzProtocolDecoderFiatMarelli {
|
|||||||
ManchesterState manchester_state;
|
ManchesterState manchester_state;
|
||||||
uint8_t decoder_state;
|
uint8_t decoder_state;
|
||||||
uint16_t preamble_count;
|
uint16_t preamble_count;
|
||||||
uint8_t raw_data[13]; // Up to 104 bits (13 bytes)
|
uint8_t raw_data[13];
|
||||||
uint8_t bit_count;
|
uint8_t bit_count;
|
||||||
uint32_t extra_data; // Bits beyond first 64, right-aligned
|
uint32_t extra_data;
|
||||||
uint32_t te_last;
|
uint32_t te_last;
|
||||||
// Auto-TE detection
|
uint32_t te_sum;
|
||||||
uint32_t te_sum; // Sum of preamble pulse durations
|
uint16_t te_count;
|
||||||
uint16_t te_count; // Number of preamble pulses averaged
|
uint32_t te_detected;
|
||||||
uint32_t te_detected; // Auto-detected te_short (0 = not yet detected)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SubGhzProtocolEncoderFiatMarelli {
|
struct SubGhzProtocolEncoderFiatMarelli {
|
||||||
SubGhzProtocolEncoderBase base;
|
SubGhzProtocolEncoderBase base;
|
||||||
SubGhzProtocolBlockEncoder encoder;
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
SubGhzBlockGeneric generic;
|
SubGhzBlockGeneric generic;
|
||||||
|
uint8_t raw_data[13];
|
||||||
|
uint32_t extra_data;
|
||||||
|
uint8_t bit_count;
|
||||||
|
uint32_t te_detected;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
@@ -87,13 +71,9 @@ typedef enum {
|
|||||||
FiatMarelliDecoderStepPreamble = 1,
|
FiatMarelliDecoderStepPreamble = 1,
|
||||||
FiatMarelliDecoderStepSync = 2,
|
FiatMarelliDecoderStepSync = 2,
|
||||||
FiatMarelliDecoderStepData = 3,
|
FiatMarelliDecoderStepData = 3,
|
||||||
FiatMarelliDecoderStepRetxSync = 4, // Waiting for sync after large gap (no preamble)
|
FiatMarelliDecoderStepRetxSync = 4,
|
||||||
} FiatMarelliDecoderStep;
|
} FiatMarelliDecoderStep;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// PROTOCOL INTERFACE DEFINITIONS
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
|
const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = {
|
||||||
.alloc = subghz_protocol_decoder_fiat_marelli_alloc,
|
.alloc = subghz_protocol_decoder_fiat_marelli_alloc,
|
||||||
.free = subghz_protocol_decoder_fiat_marelli_free,
|
.free = subghz_protocol_decoder_fiat_marelli_free,
|
||||||
@@ -117,21 +97,29 @@ const SubGhzProtocol subghz_protocol_fiat_marelli = {
|
|||||||
.name = FIAT_MARELLI_PROTOCOL_NAME,
|
.name = FIAT_MARELLI_PROTOCOL_NAME,
|
||||||
.type = SubGhzProtocolTypeDynamic,
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||||
.decoder = &subghz_protocol_fiat_marelli_decoder,
|
.decoder = &subghz_protocol_fiat_marelli_decoder,
|
||||||
.encoder = &subghz_protocol_fiat_marelli_encoder,
|
.encoder = &subghz_protocol_fiat_marelli_encoder,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// ENCODER STUBS (decode-only protocol)
|
// Encoder
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
|
#define FIAT_MARELLI_ENCODER_UPLOAD_MAX 1500
|
||||||
|
#define FIAT_MARELLI_ENCODER_REPEAT 3
|
||||||
|
#define FIAT_MARELLI_PREAMBLE_PAIRS 100
|
||||||
|
|
||||||
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment) {
|
||||||
UNUSED(environment);
|
UNUSED(environment);
|
||||||
SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli));
|
SubGhzProtocolEncoderFiatMarelli* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatMarelli));
|
||||||
furi_check(instance);
|
furi_check(instance);
|
||||||
instance->base.protocol = &subghz_protocol_fiat_marelli;
|
instance->base.protocol = &subghz_protocol_fiat_marelli;
|
||||||
instance->generic.protocol_name = instance->base.protocol->name;
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
|
||||||
|
instance->encoder.size_upload = FIAT_MARELLI_ENCODER_UPLOAD_MAX;
|
||||||
|
instance->encoder.upload = malloc(FIAT_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||||
|
furi_check(instance->encoder.upload);
|
||||||
instance->encoder.is_running = false;
|
instance->encoder.is_running = false;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -139,14 +127,142 @@ void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment)
|
|||||||
void subghz_protocol_encoder_fiat_marelli_free(void* context) {
|
void subghz_protocol_encoder_fiat_marelli_free(void* context) {
|
||||||
furi_check(context);
|
furi_check(context);
|
||||||
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
|
free(instance->encoder.upload);
|
||||||
free(instance);
|
free(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manchester encoding from decoder FSM:
|
||||||
|
// From Mid1: bit 1 = LOW_TE + HIGH_TE, bit 0 = LOW_2TE
|
||||||
|
// From Mid0: bit 0 = HIGH_TE + LOW_TE, bit 1 = HIGH_2TE
|
||||||
|
static bool fiat_marelli_encoder_get_upload(SubGhzProtocolEncoderFiatMarelli* instance) {
|
||||||
|
uint32_t te = instance->te_detected;
|
||||||
|
if(te == 0) te = subghz_protocol_fiat_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 = FIAT_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 < FIAT_MARELLI_MIN_DATA_BITS || data_bits > FIAT_MARELLI_MAX_DATA_BITS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(uint8_t i = 0; i < FIAT_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||||
|
if(i < FIAT_MARELLI_PREAMBLE_PAIRS - 1) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index < max_upload) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(false, te_short + gap_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(index < max_upload) {
|
||||||
|
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fiat_marelli_encoder_rebuild_raw_data(SubGhzProtocolEncoderFiatMarelli* 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
|
SubGhzProtocolStatus
|
||||||
subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
|
subghz_protocol_encoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||||
UNUSED(context);
|
furi_check(context);
|
||||||
UNUSED(flipper_format);
|
SubGhzProtocolEncoderFiatMarelli* instance = context;
|
||||||
return SubGhzProtocolStatusError;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
fiat_marelli_encoder_rebuild_raw_data(instance);
|
||||||
|
|
||||||
|
if(!fiat_marelli_encoder_get_upload(instance)) {
|
||||||
|
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance->encoder.repeat = FIAT_MARELLI_ENCODER_REPEAT;
|
||||||
|
instance->encoder.front = 0;
|
||||||
|
instance->encoder.is_running = true;
|
||||||
|
|
||||||
|
} while(false);
|
||||||
|
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
|
void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
|
||||||
@@ -156,25 +272,38 @@ void subghz_protocol_encoder_fiat_marelli_stop(void* context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) {
|
LevelDuration subghz_protocol_encoder_fiat_marelli_yield(void* context) {
|
||||||
UNUSED(context);
|
furi_check(context);
|
||||||
return level_duration_reset();
|
SubGhzProtocolEncoderFiatMarelli* 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 IMPLEMENTATION
|
// Decoder
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// Helper: rebuild raw_data[] from generic.data + extra_data
|
|
||||||
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||||
|
|
||||||
// First 64 bits from generic.data
|
|
||||||
uint64_t key = instance->generic.data;
|
uint64_t key = instance->generic.data;
|
||||||
for(int i = 0; i < 8; i++) {
|
for(int i = 0; i < 8; i++) {
|
||||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining bits from extra_data (right-aligned)
|
|
||||||
uint8_t extra_bits =
|
uint8_t extra_bits =
|
||||||
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
||||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||||
@@ -187,7 +316,6 @@ static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* inst
|
|||||||
|
|
||||||
instance->bit_count = instance->generic.data_count_bit;
|
instance->bit_count = instance->generic.data_count_bit;
|
||||||
|
|
||||||
// Re-extract protocol fields from raw_data (needed after deserialize)
|
|
||||||
if(instance->bit_count >= 56) {
|
if(instance->bit_count >= 56) {
|
||||||
instance->generic.serial =
|
instance->generic.serial =
|
||||||
((uint32_t)instance->raw_data[2] << 24) |
|
((uint32_t)instance->raw_data[2] << 24) |
|
||||||
@@ -199,7 +327,6 @@ static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* inst
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper: prepare data collection state for Manchester decoding
|
|
||||||
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) {
|
||||||
instance->bit_count = 0;
|
instance->bit_count = 0;
|
||||||
instance->extra_data = 0;
|
instance->extra_data = 0;
|
||||||
@@ -249,12 +376,9 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
furi_check(context);
|
furi_check(context);
|
||||||
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
SubGhzProtocolDecoderFiatMarelli* instance = context;
|
||||||
|
|
||||||
// Use auto-detected TE if available, otherwise fall back to defaults
|
|
||||||
uint32_t te_short = instance->te_detected ? instance->te_detected
|
uint32_t te_short = instance->te_detected ? instance->te_detected
|
||||||
: (uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
: (uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
||||||
uint32_t te_long = te_short * 2;
|
uint32_t te_long = te_short * 2;
|
||||||
// Delta = te_short/2: maximum that avoids short/long overlap (boundary at 1.5*TE).
|
|
||||||
// Must be this wide for Type B asymmetric timing (pos~140us, neg~68us, avg~100us).
|
|
||||||
uint32_t te_delta = te_short / 2;
|
uint32_t te_delta = te_short / 2;
|
||||||
if(te_delta < 30) te_delta = 30;
|
if(te_delta < 30) te_delta = 30;
|
||||||
uint32_t diff;
|
uint32_t diff;
|
||||||
@@ -262,7 +386,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
switch(instance->decoder_state) {
|
switch(instance->decoder_state) {
|
||||||
case FiatMarelliDecoderStepReset:
|
case FiatMarelliDecoderStepReset:
|
||||||
if(level) {
|
if(level) {
|
||||||
// Check for preamble-like short HIGH pulse (50-350us range)
|
|
||||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||||
instance->decoder_state = FiatMarelliDecoderStepPreamble;
|
instance->decoder_state = FiatMarelliDecoderStepPreamble;
|
||||||
@@ -272,7 +395,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Large LOW gap without preamble -> retransmission path
|
|
||||||
if(duration > FIAT_MARELLI_RETX_GAP_MIN) {
|
if(duration > FIAT_MARELLI_RETX_GAP_MIN) {
|
||||||
instance->decoder_state = FiatMarelliDecoderStepRetxSync;
|
instance->decoder_state = FiatMarelliDecoderStepRetxSync;
|
||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
@@ -283,20 +405,16 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
case FiatMarelliDecoderStepPreamble:
|
case FiatMarelliDecoderStepPreamble:
|
||||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||||
// Short pulse (HIGH or LOW) - preamble continues
|
|
||||||
instance->preamble_count++;
|
instance->preamble_count++;
|
||||||
instance->te_sum += duration;
|
instance->te_sum += duration;
|
||||||
instance->te_count++;
|
instance->te_count++;
|
||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
} else if(!level) {
|
} else if(!level) {
|
||||||
// Non-short LOW pulse - could be gap after preamble
|
|
||||||
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
|
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
|
||||||
// Compute auto-detected TE from preamble average
|
|
||||||
instance->te_detected = instance->te_sum / instance->te_count;
|
instance->te_detected = instance->te_sum / instance->te_count;
|
||||||
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
|
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
|
||||||
|
|
||||||
if(duration > gap_threshold) {
|
if(duration > gap_threshold) {
|
||||||
// Gap detected - wait for sync
|
|
||||||
instance->decoder_state = FiatMarelliDecoderStepSync;
|
instance->decoder_state = FiatMarelliDecoderStepSync;
|
||||||
instance->te_last = duration;
|
instance->te_last = duration;
|
||||||
} else {
|
} else {
|
||||||
@@ -306,13 +424,11 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Non-short HIGH pulse during preamble - reset
|
|
||||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FiatMarelliDecoderStepSync: {
|
case FiatMarelliDecoderStepSync: {
|
||||||
// Expect sync HIGH pulse (scaled to detected TE)
|
|
||||||
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
|
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
|
||||||
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
|
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
|
||||||
|
|
||||||
@@ -326,14 +442,10 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
}
|
}
|
||||||
|
|
||||||
case FiatMarelliDecoderStepRetxSync:
|
case FiatMarelliDecoderStepRetxSync:
|
||||||
// Retransmission path: expect sync HIGH pulse after large gap
|
|
||||||
// Use broad range since we don't know TE yet
|
|
||||||
if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN &&
|
if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN &&
|
||||||
duration <= FIAT_MARELLI_RETX_SYNC_MAX) {
|
duration <= FIAT_MARELLI_RETX_SYNC_MAX) {
|
||||||
// Auto-detect TE from sync pulse (sync is ~8x TE)
|
|
||||||
if(!instance->te_detected) {
|
if(!instance->te_detected) {
|
||||||
instance->te_detected = duration / 8;
|
instance->te_detected = duration / 8;
|
||||||
// Clamp to reasonable range
|
|
||||||
if(instance->te_detected < 70) instance->te_detected = 100;
|
if(instance->te_detected < 70) instance->te_detected = 100;
|
||||||
if(instance->te_detected > 350) instance->te_detected = 260;
|
if(instance->te_detected > 350) instance->te_detected = 260;
|
||||||
}
|
}
|
||||||
@@ -348,7 +460,6 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
ManchesterEvent event = ManchesterEventReset;
|
ManchesterEvent event = ManchesterEventReset;
|
||||||
bool frame_complete = false;
|
bool frame_complete = false;
|
||||||
|
|
||||||
// Classify duration as short or long Manchester edge using detected TE
|
|
||||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||||
if(diff < te_delta) {
|
if(diff < te_delta) {
|
||||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||||
@@ -399,18 +510,12 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
|||||||
if(frame_complete) {
|
if(frame_complete) {
|
||||||
instance->generic.data_count_bit = instance->bit_count;
|
instance->generic.data_count_bit = instance->bit_count;
|
||||||
|
|
||||||
// Frame layout: bytes 0-1 are preamble residue (0xFFFF or 0xFFFC)
|
|
||||||
// Bytes 2-5: Fixed ID (serial)
|
|
||||||
// Byte 6: [Button:4 | Epoch:4]
|
|
||||||
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
|
||||||
// Bytes 8-12: Encrypted payload (40 bits)
|
|
||||||
instance->generic.serial =
|
instance->generic.serial =
|
||||||
((uint32_t)instance->raw_data[2] << 24) |
|
((uint32_t)instance->raw_data[2] << 24) |
|
||||||
((uint32_t)instance->raw_data[3] << 16) |
|
((uint32_t)instance->raw_data[3] << 16) |
|
||||||
((uint32_t)instance->raw_data[4] << 8) |
|
((uint32_t)instance->raw_data[4] << 8) |
|
||||||
((uint32_t)instance->raw_data[5]);
|
((uint32_t)instance->raw_data[5]);
|
||||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||||
// cnt: 5-bit plaintext counter from byte 7 upper bits
|
|
||||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||||
|
|
||||||
const char* variant = (instance->te_detected &&
|
const char* variant = (instance->te_detected &&
|
||||||
@@ -471,16 +576,13 @@ SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize(
|
|||||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
|
||||||
if(ret == SubGhzProtocolStatusOk) {
|
if(ret == SubGhzProtocolStatusOk) {
|
||||||
// Save extra data (bits 64+ right-aligned in uint32_t)
|
|
||||||
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
||||||
|
|
||||||
// Save total bit count explicitly (generic serialize also saves it, but Extra needs context)
|
|
||||||
uint32_t extra_bits = instance->generic.data_count_bit > 64
|
uint32_t extra_bits = instance->generic.data_count_bit > 64
|
||||||
? (instance->generic.data_count_bit - 64)
|
? (instance->generic.data_count_bit - 64)
|
||||||
: 0;
|
: 0;
|
||||||
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
||||||
|
|
||||||
// Save detected TE for variant identification on reload
|
|
||||||
uint32_t te = instance->te_detected;
|
uint32_t te = instance->te_detected;
|
||||||
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
||||||
}
|
}
|
||||||
@@ -544,17 +646,17 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
|
|||||||
|
|
||||||
furi_string_cat_printf(
|
furi_string_cat_printf(
|
||||||
output,
|
output,
|
||||||
"%s %dbit Type%s\r\n"
|
"%s %dbit\r\n"
|
||||||
"Sn:%08lX Btn:%s\r\n"
|
"Sn:%08lX Btn:%s\r\n"
|
||||||
"Ep:%X Ctr:%02d\r\n"
|
"Ep:%X Ctr:%02d Type%s\r\n"
|
||||||
"R:%02X%02X%02X%02X%02X%02X",
|
"R:%02X%02X%02X%02X%02X%02X",
|
||||||
instance->generic.protocol_name,
|
instance->generic.protocol_name,
|
||||||
instance->bit_count,
|
instance->bit_count,
|
||||||
variant,
|
|
||||||
instance->generic.serial,
|
instance->generic.serial,
|
||||||
fiat_marelli_button_name(instance->generic.btn),
|
fiat_marelli_button_name(instance->generic.btn),
|
||||||
epoch,
|
epoch,
|
||||||
counter,
|
counter,
|
||||||
|
variant,
|
||||||
instance->raw_data[7],
|
instance->raw_data[7],
|
||||||
instance->raw_data[8],
|
instance->raw_data[8],
|
||||||
instance->raw_data[9],
|
instance->raw_data[9],
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ SubGhzProtocolStatus
|
|||||||
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||||
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output);
|
||||||
|
|
||||||
// Encoder stubs
|
// Encoder (replay of captured frames)
|
||||||
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
void* subghz_protocol_encoder_fiat_marelli_alloc(SubGhzEnvironment* environment);
|
||||||
void subghz_protocol_encoder_fiat_marelli_free(void* context);
|
void subghz_protocol_encoder_fiat_marelli_free(void* context);
|
||||||
SubGhzProtocolStatus
|
SubGhzProtocolStatus
|
||||||
|
|||||||
274
lib/subghz/protocols/honda.c
Normal file
274
lib/subghz/protocols/honda.c
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
#include "honda.h"
|
||||||
|
|
||||||
|
#define TAG "SubGhzProtocolHonda"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_honda_const = {
|
||||||
|
.te_short = 432, // Short pulse ~432µs
|
||||||
|
.te_long = 864, // Long pulse ~864µs (2x short)
|
||||||
|
.te_delta = 150, // Tolerance
|
||||||
|
.min_count_bit_for_found = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderHonda {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint16_t header_count;
|
||||||
|
} SubGhzProtocolDecoderHonda;
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolEncoderHonda {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
} SubGhzProtocolEncoderHonda;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
HondaDecoderStepReset = 0,
|
||||||
|
HondaDecoderStepCheckPreamble,
|
||||||
|
HondaDecoderStepSaveDuration,
|
||||||
|
HondaDecoderStepCheckDuration,
|
||||||
|
} HondaDecoderStep;
|
||||||
|
|
||||||
|
static void subghz_protocol_decoder_honda_reset_internal(SubGhzProtocolDecoderHonda* instance) {
|
||||||
|
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||||
|
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
instance->header_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_honda_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_honda_alloc,
|
||||||
|
.free = subghz_protocol_decoder_honda_free,
|
||||||
|
|
||||||
|
.feed = subghz_protocol_decoder_honda_feed,
|
||||||
|
.reset = subghz_protocol_decoder_honda_reset,
|
||||||
|
|
||||||
|
.get_hash_data = subghz_protocol_decoder_honda_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_honda_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_honda_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_honda_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_honda_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_honda = {
|
||||||
|
.name = HONDA_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic, // Rolling code (vulnerable)
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &subghz_protocol_honda_decoder,
|
||||||
|
.encoder = &subghz_protocol_honda_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------- Allocation / Reset / Free -------------------
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = calloc(1, sizeof(SubGhzProtocolDecoderHonda));
|
||||||
|
instance->base.protocol = &subghz_protocol_honda;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
subghz_protocol_decoder_honda_reset(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
subghz_protocol_decoder_honda_reset_internal(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Honda Protocol Parsing -------------------
|
||||||
|
|
||||||
|
static bool subghz_protocol_honda_parse_data(SubGhzProtocolDecoderHonda* instance) {
|
||||||
|
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||||
|
|
||||||
|
// Honda protocol structure (from rtl_433):
|
||||||
|
// Bits 0-7: Preamble/sync
|
||||||
|
// Bits 8-39: Device ID (32 bits)
|
||||||
|
// Bits 40-55: Rolling counter (16 bits)
|
||||||
|
// Bits 56-63: Function code (8 bits) - which button was pressed
|
||||||
|
|
||||||
|
// Extract device ID (bytes 1-4)
|
||||||
|
uint32_t device_id = ((uint32_t)b[1] << 24) |
|
||||||
|
(b[2] << 16) |
|
||||||
|
(b[3] << 8) |
|
||||||
|
b[4];
|
||||||
|
|
||||||
|
// Extract rolling counter (bytes 5-6)
|
||||||
|
uint16_t rolling_counter = (b[5] << 8) | b[6];
|
||||||
|
|
||||||
|
// Extract function code (byte 7)
|
||||||
|
uint8_t function = b[7];
|
||||||
|
|
||||||
|
// Store parsed data
|
||||||
|
instance->generic.serial = device_id;
|
||||||
|
instance->generic.cnt = rolling_counter;
|
||||||
|
instance->generic.btn = function;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Decoder Feed -------------------
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case HondaDecoderStepReset:
|
||||||
|
if(level && (DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta)) {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepCheckPreamble;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HondaDecoderStepCheckPreamble:
|
||||||
|
if(level) {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Looking for preamble pattern
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta)) {
|
||||||
|
instance->header_count++;
|
||||||
|
} else if((DURATION_DIFF(duration, subghz_protocol_honda_const.te_long) <
|
||||||
|
subghz_protocol_honda_const.te_delta * 2) &&
|
||||||
|
instance->header_count >= 10) {
|
||||||
|
// Long gap after preamble - start of data
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0ULL;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HondaDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
if(duration >= (subghz_protocol_honda_const.te_long * 3)) {
|
||||||
|
// End of transmission
|
||||||
|
if(instance->decoder.decode_count_bit >=
|
||||||
|
subghz_protocol_honda_const.min_count_bit_for_found) {
|
||||||
|
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
|
||||||
|
// Parse Honda protocol structure
|
||||||
|
if(subghz_protocol_honda_parse_data(instance)) {
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subghz_protocol_decoder_honda_reset_internal(instance);
|
||||||
|
} else {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepCheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HondaDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
// Manchester decoding (differential)
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_honda_const.te_long) <
|
||||||
|
subghz_protocol_honda_const.te_delta)) {
|
||||||
|
// Short-Long = 0
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_honda_const.te_long) <
|
||||||
|
subghz_protocol_honda_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_honda_const.te_short) <
|
||||||
|
subghz_protocol_honda_const.te_delta)) {
|
||||||
|
// Long-Short = 1
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = HondaDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- API -------------------
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
return subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic,
|
||||||
|
flipper_format,
|
||||||
|
subghz_protocol_honda_const.min_count_bit_for_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderHonda* instance = context;
|
||||||
|
|
||||||
|
uint32_t hi = instance->generic.data >> 32;
|
||||||
|
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Key:%08lX%08lX\r\n"
|
||||||
|
"ID:%08lX Btn:%02X Cnt:%04X\r\n"
|
||||||
|
"CVE:CVE-2022-27254\r\n"
|
||||||
|
"Note:Rolling code vulnerable\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
hi,
|
||||||
|
lo,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
(uint16_t)instance->generic.cnt);
|
||||||
|
}
|
||||||
77
lib/subghz/protocols/honda.h
Normal file
77
lib/subghz/protocols/honda.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
|
||||||
|
#define HONDA_PROTOCOL_NAME "Honda"
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_honda;
|
||||||
|
extern const SubGhzProtocolDecoder subghz_protocol_honda_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_honda_encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates memory for the Honda protocol decoder.
|
||||||
|
* @param environment Pointer to SubGhzEnvironment
|
||||||
|
* @return Pointer to the allocated decoder instance
|
||||||
|
*/
|
||||||
|
void* subghz_protocol_decoder_honda_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory used by the Honda protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_honda_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Honda protocol decoder state.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_honda_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feeds a pulse/gap into the Honda protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param level Signal level (true = high, false = low)
|
||||||
|
* @param duration Duration of the level in microseconds
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_honda_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hash of the decoded Honda data.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @return Hash byte
|
||||||
|
*/
|
||||||
|
uint8_t subghz_protocol_decoder_honda_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the decoded Honda data into a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @param preset Pointer to the radio preset
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes Honda data from a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_honda_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the decoded Honda data into a human-readable string.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param output Pointer to the FuriString output buffer
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_honda_get_string(void* context, FuriString* output);
|
||||||
259
lib/subghz/protocols/mitsubishi_v1.c
Normal file
259
lib/subghz/protocols/mitsubishi_v1.c
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
#include "mitsubishi_v1.h"
|
||||||
|
|
||||||
|
#define TAG "SubGhzProtocolMitsubishi"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_mitsubishi_const = {
|
||||||
|
.te_short = 320, // Similar to KIA timing
|
||||||
|
.te_long = 640, // ~2× te_short
|
||||||
|
.te_delta = 100,
|
||||||
|
.min_count_bit_for_found = 64,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderMitsubishi {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint16_t header_count;
|
||||||
|
} SubGhzProtocolDecoderMitsubishi;
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolEncoderMitsubishi {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
} SubGhzProtocolEncoderMitsubishi;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MitsubishiDecoderStepReset = 0,
|
||||||
|
MitsubishiDecoderStepCheckPreamble,
|
||||||
|
MitsubishiDecoderStepSaveDuration,
|
||||||
|
MitsubishiDecoderStepCheckDuration,
|
||||||
|
} MitsubishiDecoderStep;
|
||||||
|
|
||||||
|
static void subghz_protocol_decoder_mitsubishi_reset_internal(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||||
|
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||||
|
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
instance->header_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_mitsubishi_alloc,
|
||||||
|
.free = subghz_protocol_decoder_mitsubishi_free,
|
||||||
|
|
||||||
|
.feed = subghz_protocol_decoder_mitsubishi_feed,
|
||||||
|
.reset = subghz_protocol_decoder_mitsubishi_reset,
|
||||||
|
|
||||||
|
.get_hash_data = subghz_protocol_decoder_mitsubishi_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_mitsubishi_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_mitsubishi_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_mitsubishi_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_mitsubishi_v1 = {
|
||||||
|
.name = MITSUBISHI_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &subghz_protocol_mitsubishi_decoder,
|
||||||
|
.encoder = &subghz_protocol_mitsubishi_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------- Allocation / Reset / Free -------------------
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi));
|
||||||
|
instance->base.protocol = &subghz_protocol_mitsubishi_v1;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
subghz_protocol_decoder_mitsubishi_reset(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_mitsubishi_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_mitsubishi_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
subghz_protocol_decoder_mitsubishi_reset_internal(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Helper Functions -------------------
|
||||||
|
|
||||||
|
// Parse Mitsubishi/KIA-Hyundai data structure
|
||||||
|
static void subghz_protocol_mitsubishi_parse_data(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||||
|
// Structure similar to KIA/Hyundai protocol
|
||||||
|
// Serial number in upper bits
|
||||||
|
// Button code in middle bits
|
||||||
|
// Counter in lower bits
|
||||||
|
|
||||||
|
instance->generic.serial = (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF);
|
||||||
|
instance->generic.btn = (instance->generic.data >> 24) & 0xFF;
|
||||||
|
instance->generic.cnt = (instance->generic.data >> 8) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Decoder Feed -------------------
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case MitsubishiDecoderStepReset:
|
||||||
|
if(level && (DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepCheckPreamble;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MitsubishiDecoderStepCheckPreamble:
|
||||||
|
if(level) {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta) ||
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
instance->header_count++;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_long) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
if(instance->header_count > 10) {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0ULL;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MitsubishiDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
if(duration >= (subghz_protocol_mitsubishi_const.te_long * 3)) {
|
||||||
|
if(instance->decoder.decode_count_bit >=
|
||||||
|
subghz_protocol_mitsubishi_const.min_count_bit_for_found) {
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
|
||||||
|
// Parse Mitsubishi data
|
||||||
|
subghz_protocol_mitsubishi_parse_data(instance);
|
||||||
|
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subghz_protocol_decoder_mitsubishi_reset_internal(instance);
|
||||||
|
} else {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepCheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MitsubishiDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
// Manchester-like decoding (KIA/Hyundai style)
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_mitsubishi_const.te_long) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||||
|
subghz_protocol_mitsubishi_const.te_delta)) {
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = MitsubishiDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- API -------------------
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
return subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic,
|
||||||
|
flipper_format,
|
||||||
|
subghz_protocol_mitsubishi_const.min_count_bit_for_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||||
|
|
||||||
|
uint32_t hi = instance->generic.data >> 32;
|
||||||
|
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Key:%08lX%08lX\r\n"
|
||||||
|
"Sn:%08lX Btn:%02X Cnt:%04lX\r\n"
|
||||||
|
"Type:KIA/Hyundai based\r\n"
|
||||||
|
"Models:L200,Pajero,ASX+\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
hi,
|
||||||
|
lo,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
instance->generic.cnt);
|
||||||
|
}
|
||||||
77
lib/subghz/protocols/mitsubishi_v1.h
Normal file
77
lib/subghz/protocols/mitsubishi_v1.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
|
||||||
|
#define MITSUBISHI_PROTOCOL_NAME "Mitsubishi v1"
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_mitsubishi_v1;
|
||||||
|
extern const SubGhzProtocolDecoder subghz_protocol_mitsubishi_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_mitsubishi_encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates memory for the Mitsubishi protocol decoder.
|
||||||
|
* @param environment Pointer to SubGhzEnvironment
|
||||||
|
* @return Pointer to the allocated decoder instance
|
||||||
|
*/
|
||||||
|
void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory used by the Mitsubishi protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_mitsubishi_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Mitsubishi protocol decoder state.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_mitsubishi_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feeds a pulse/gap into the Mitsubishi protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param level Signal level (true = high, false = low)
|
||||||
|
* @param duration Duration of the level in microseconds
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hash of the decoded Mitsubishi data.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @return Hash byte
|
||||||
|
*/
|
||||||
|
uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the decoded Mitsubishi data into a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @param preset Pointer to the radio preset
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes Mitsubishi data from a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the decoded Mitsubishi data into a human-readable string.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param output Pointer to the FuriString output buffer
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output);
|
||||||
291
lib/subghz/protocols/peugeot.c
Normal file
291
lib/subghz/protocols/peugeot.c
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
#include "peugeot.h"
|
||||||
|
|
||||||
|
#define TAG "SubGhzProtocolPeugeot"
|
||||||
|
|
||||||
|
static const SubGhzBlockConst subghz_protocol_peugeot_const = {
|
||||||
|
.te_short = 370, // Short pulse duration
|
||||||
|
.te_long = 772, // Long pulse duration (~2x short)
|
||||||
|
.te_delta = 152, // Tolerance
|
||||||
|
.min_count_bit_for_found = 66,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolDecoderPeugeot {
|
||||||
|
SubGhzProtocolDecoderBase base;
|
||||||
|
SubGhzBlockDecoder decoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
uint16_t header_count;
|
||||||
|
uint8_t packet_count;
|
||||||
|
} SubGhzProtocolDecoderPeugeot;
|
||||||
|
|
||||||
|
typedef struct SubGhzProtocolEncoderPeugeot {
|
||||||
|
SubGhzProtocolEncoderBase base;
|
||||||
|
SubGhzProtocolBlockEncoder encoder;
|
||||||
|
SubGhzBlockGeneric generic;
|
||||||
|
} SubGhzProtocolEncoderPeugeot;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PeugeotDecoderStepReset = 0,
|
||||||
|
PeugeotDecoderStepCheckPreamble,
|
||||||
|
PeugeotDecoderStepSaveDuration,
|
||||||
|
PeugeotDecoderStepCheckDuration,
|
||||||
|
} PeugeotDecoderStep;
|
||||||
|
|
||||||
|
static void subghz_protocol_decoder_peugeot_reset_internal(SubGhzProtocolDecoderPeugeot* instance) {
|
||||||
|
memset(&instance->decoder, 0, sizeof(instance->decoder));
|
||||||
|
memset(&instance->generic, 0, sizeof(instance->generic));
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->packet_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SubGhzProtocolDecoder subghz_protocol_peugeot_decoder = {
|
||||||
|
.alloc = subghz_protocol_decoder_peugeot_alloc,
|
||||||
|
.free = subghz_protocol_decoder_peugeot_free,
|
||||||
|
|
||||||
|
.feed = subghz_protocol_decoder_peugeot_feed,
|
||||||
|
.reset = subghz_protocol_decoder_peugeot_reset,
|
||||||
|
|
||||||
|
.get_hash_data = subghz_protocol_decoder_peugeot_get_hash_data,
|
||||||
|
.serialize = subghz_protocol_decoder_peugeot_serialize,
|
||||||
|
.deserialize = subghz_protocol_decoder_peugeot_deserialize,
|
||||||
|
.get_string = subghz_protocol_decoder_peugeot_get_string,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocolEncoder subghz_protocol_peugeot_encoder = {
|
||||||
|
.alloc = NULL,
|
||||||
|
.free = NULL,
|
||||||
|
|
||||||
|
.deserialize = NULL,
|
||||||
|
.stop = NULL,
|
||||||
|
.yield = NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const SubGhzProtocol subghz_protocol_peugeot = {
|
||||||
|
.name = PEUGEOT_PROTOCOL_NAME,
|
||||||
|
.type = SubGhzProtocolTypeDynamic,
|
||||||
|
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||||
|
|
||||||
|
.decoder = &subghz_protocol_peugeot_decoder,
|
||||||
|
.encoder = &subghz_protocol_peugeot_encoder,
|
||||||
|
};
|
||||||
|
|
||||||
|
// ----------------- Allocation / Reset / Free -------------------
|
||||||
|
|
||||||
|
void* subghz_protocol_decoder_peugeot_alloc(SubGhzEnvironment* environment) {
|
||||||
|
UNUSED(environment);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = calloc(1, sizeof(SubGhzProtocolDecoderPeugeot));
|
||||||
|
instance->base.protocol = &subghz_protocol_peugeot;
|
||||||
|
instance->generic.protocol_name = instance->base.protocol->name;
|
||||||
|
subghz_protocol_decoder_peugeot_reset(instance);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_peugeot_free(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
free(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_peugeot_reset(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
subghz_protocol_decoder_peugeot_reset_internal(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Helper Functions -------------------
|
||||||
|
|
||||||
|
// Reverse 8 bits (LSB to MSB)
|
||||||
|
static uint8_t reverse8(uint8_t byte) {
|
||||||
|
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||||
|
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||||
|
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Keeloq data structure
|
||||||
|
static bool subghz_protocol_peugeot_parse_data(SubGhzProtocolDecoderPeugeot* instance) {
|
||||||
|
uint8_t* b = (uint8_t*)&instance->generic.data;
|
||||||
|
|
||||||
|
// Check preamble (first 12 bits should be 0xFFF)
|
||||||
|
if(b[0] != 0xFF || (b[1] & 0xF0) != 0xF0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract encrypted part (32 bits) - reversed
|
||||||
|
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) |
|
||||||
|
(reverse8(b[2]) << 16) |
|
||||||
|
(reverse8(b[1] & 0x0F) << 8) |
|
||||||
|
reverse8(b[0]);
|
||||||
|
|
||||||
|
// Extract serial number (28 bits) - reversed
|
||||||
|
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 20) |
|
||||||
|
(reverse8(b[6]) << 12) |
|
||||||
|
(reverse8(b[5]) << 4) |
|
||||||
|
(reverse8(b[4]) >> 4);
|
||||||
|
|
||||||
|
// Extract button bits (4 bits from encrypted part)
|
||||||
|
// Note: Button bits are (MSB/first sent to LSB) S3, S0, S1, S2
|
||||||
|
uint8_t button_bits = (encrypted >> 28) & 0x0F;
|
||||||
|
|
||||||
|
// Store parsed data
|
||||||
|
instance->generic.serial = serial;
|
||||||
|
instance->generic.btn = button_bits;
|
||||||
|
instance->generic.cnt = (encrypted >> 16) & 0xFFFF; // Counter from encrypted part
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- Decoder Feed -------------------
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_peugeot_feed(void* context, bool level, uint32_t duration) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
|
||||||
|
switch(instance->decoder.parser_step) {
|
||||||
|
case PeugeotDecoderStepReset:
|
||||||
|
if(level && (DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta)) {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepCheckPreamble;
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->header_count = 0;
|
||||||
|
instance->decoder.decode_data = 0;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PeugeotDecoderStepCheckPreamble:
|
||||||
|
if(level) {
|
||||||
|
// High level - save duration
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta)) {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Low level - check for warm-up pulses
|
||||||
|
if((DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta)) {
|
||||||
|
// Short pulse pair - part of warm-up
|
||||||
|
instance->header_count++;
|
||||||
|
} else if((DURATION_DIFF(duration, 4400) < 500) && instance->header_count >= 10) {
|
||||||
|
// Long gap after warm-up pulses (~4400µs)
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||||
|
instance->decoder.decode_data = 0ULL;
|
||||||
|
instance->decoder.decode_count_bit = 0;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PeugeotDecoderStepSaveDuration:
|
||||||
|
if(level) {
|
||||||
|
// High level - save duration
|
||||||
|
if(duration >= (subghz_protocol_peugeot_const.te_long * 3)) {
|
||||||
|
// Very long pulse - end of packet
|
||||||
|
if(instance->decoder.decode_count_bit >=
|
||||||
|
subghz_protocol_peugeot_const.min_count_bit_for_found) {
|
||||||
|
|
||||||
|
instance->generic.data = instance->decoder.decode_data;
|
||||||
|
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||||
|
|
||||||
|
// Parse the Keeloq structure
|
||||||
|
if(subghz_protocol_peugeot_parse_data(instance)) {
|
||||||
|
instance->packet_count++;
|
||||||
|
|
||||||
|
// Call callback after receiving at least one packet
|
||||||
|
if(instance->base.callback) {
|
||||||
|
instance->base.callback(&instance->base, instance->base.context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subghz_protocol_decoder_peugeot_reset_internal(instance);
|
||||||
|
} else {
|
||||||
|
instance->decoder.te_last = duration;
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepCheckDuration;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PeugeotDecoderStepCheckDuration:
|
||||||
|
if(!level) {
|
||||||
|
// PWM decoding: short-long = 0, long-short = 1
|
||||||
|
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_long) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta)) {
|
||||||
|
// Short high, long low = 0
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||||
|
} else if(
|
||||||
|
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_peugeot_const.te_long) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta) &&
|
||||||
|
(DURATION_DIFF(duration, subghz_protocol_peugeot_const.te_short) <
|
||||||
|
subghz_protocol_peugeot_const.te_delta)) {
|
||||||
|
// Long high, short low = 1
|
||||||
|
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepSaveDuration;
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
instance->decoder.parser_step = PeugeotDecoderStepReset;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------- API -------------------
|
||||||
|
|
||||||
|
uint8_t subghz_protocol_decoder_peugeot_get_hash_data(void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
return subghz_protocol_blocks_get_hash_data(
|
||||||
|
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||||
|
}
|
||||||
|
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
return subghz_block_generic_deserialize_check_count_bit(
|
||||||
|
&instance->generic,
|
||||||
|
flipper_format,
|
||||||
|
subghz_protocol_peugeot_const.min_count_bit_for_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
void subghz_protocol_decoder_peugeot_get_string(void* context, FuriString* output) {
|
||||||
|
furi_assert(context);
|
||||||
|
SubGhzProtocolDecoderPeugeot* instance = context;
|
||||||
|
|
||||||
|
uint32_t hi = instance->generic.data >> 32;
|
||||||
|
uint32_t lo = instance->generic.data & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
furi_string_cat_printf(
|
||||||
|
output,
|
||||||
|
"%s %dbit\r\n"
|
||||||
|
"Key:%08lX%08lX\r\n"
|
||||||
|
"Sn:%07lX Btn:%X Cnt:%04lX\r\n"
|
||||||
|
"Type:Keeloq/HCS\r\n",
|
||||||
|
instance->generic.protocol_name,
|
||||||
|
instance->generic.data_count_bit,
|
||||||
|
hi,
|
||||||
|
lo,
|
||||||
|
instance->generic.serial,
|
||||||
|
instance->generic.btn,
|
||||||
|
instance->generic.cnt);
|
||||||
|
}
|
||||||
77
lib/subghz/protocols/peugeot.h
Normal file
77
lib/subghz/protocols/peugeot.h
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <lib/subghz/protocols/base.h>
|
||||||
|
#include <lib/subghz/blocks/const.h>
|
||||||
|
#include <lib/subghz/blocks/decoder.h>
|
||||||
|
#include <lib/subghz/blocks/encoder.h>
|
||||||
|
#include <lib/subghz/blocks/generic.h>
|
||||||
|
#include <lib/subghz/blocks/math.h>
|
||||||
|
|
||||||
|
#define PEUGEOT_PROTOCOL_NAME "Peugeot"
|
||||||
|
|
||||||
|
extern const SubGhzProtocol subghz_protocol_peugeot;
|
||||||
|
extern const SubGhzProtocolDecoder subghz_protocol_peugeot_decoder;
|
||||||
|
extern const SubGhzProtocolEncoder subghz_protocol_peugeot_encoder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates memory for the Peugeot protocol decoder.
|
||||||
|
* @param environment Pointer to SubGhzEnvironment
|
||||||
|
* @return Pointer to the allocated decoder instance
|
||||||
|
*/
|
||||||
|
void* subghz_protocol_decoder_peugeot_alloc(SubGhzEnvironment* environment);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees memory used by the Peugeot protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_peugeot_free(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the Peugeot protocol decoder state.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_peugeot_reset(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feeds a pulse/gap into the Peugeot protocol decoder.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param level Signal level (true = high, false = low)
|
||||||
|
* @param duration Duration of the level in microseconds
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_peugeot_feed(void* context, bool level, uint32_t duration);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a hash of the decoded Peugeot data.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @return Hash byte
|
||||||
|
*/
|
||||||
|
uint8_t subghz_protocol_decoder_peugeot_get_hash_data(void* context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes the decoded Peugeot data into a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @param preset Pointer to the radio preset
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_serialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format,
|
||||||
|
SubGhzRadioPreset* preset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserializes Peugeot data from a FlipperFormat file.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param flipper_format Pointer to the FlipperFormat instance
|
||||||
|
* @return SubGhzProtocolStatus result
|
||||||
|
*/
|
||||||
|
SubGhzProtocolStatus subghz_protocol_decoder_peugeot_deserialize(
|
||||||
|
void* context,
|
||||||
|
FlipperFormat* flipper_format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the decoded Peugeot data into a human-readable string.
|
||||||
|
* @param context Pointer to the decoder instance
|
||||||
|
* @param output Pointer to the FuriString output buffer
|
||||||
|
*/
|
||||||
|
void subghz_protocol_decoder_peugeot_get_string(void* context, FuriString* output);
|
||||||
@@ -43,6 +43,8 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
|||||||
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
|
&subghz_protocol_kia_v2, &subghz_protocol_kia_v3_v4,
|
||||||
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
|
&subghz_protocol_kia_v5, &subghz_protocol_kia_v6,
|
||||||
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_v0,
|
&subghz_protocol_suzuki, &subghz_protocol_mitsubishi_v0,
|
||||||
|
&subghz_protocol_bmw, &subghz_protocol_mitsubishi_v1, &subghz_protocol_honda,
|
||||||
|
&subghz_protocol_citroen, &subghz_protocol_peugeot,
|
||||||
};
|
};
|
||||||
|
|
||||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
#include "fiat_v0.h"
|
#include "fiat_v0.h"
|
||||||
#include "fiat_marelli.h"
|
#include "fiat_marelli.h"
|
||||||
#include "subaru.h"
|
#include "subaru.h"
|
||||||
|
#include "bmw.h"
|
||||||
#include "kia_generic.h"
|
#include "kia_generic.h"
|
||||||
#include "kia_v0.h"
|
#include "kia_v0.h"
|
||||||
#include "kia_v1.h"
|
#include "kia_v1.h"
|
||||||
@@ -72,5 +73,9 @@
|
|||||||
#include "kia_v6.h"
|
#include "kia_v6.h"
|
||||||
#include "suzuki.h"
|
#include "suzuki.h"
|
||||||
#include "mitsubishi_v0.h"
|
#include "mitsubishi_v0.h"
|
||||||
|
#include "mitsubishi_v1.h"
|
||||||
|
#include "honda.h"
|
||||||
|
#include "citroen.h"
|
||||||
|
#include "peugeot.h"
|
||||||
#include "mazda_siemens.h"
|
#include "mazda_siemens.h"
|
||||||
#include "keys.h"
|
#include "keys.h"
|
||||||
|
|||||||
Reference in New Issue
Block a user