mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-03-29 11:39:54 +00:00
Compare commits
1 Commits
dev-56c567
...
dev-8bf12d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bf12df45d |
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);
|
||||
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_v5, &subghz_protocol_kia_v6,
|
||||
&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 = {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "fiat_v0.h"
|
||||
#include "fiat_marelli.h"
|
||||
#include "subaru.h"
|
||||
#include "bmw.h"
|
||||
#include "kia_generic.h"
|
||||
#include "kia_v0.h"
|
||||
#include "kia_v1.h"
|
||||
@@ -72,5 +73,9 @@
|
||||
#include "kia_v6.h"
|
||||
#include "suzuki.h"
|
||||
#include "mitsubishi_v0.h"
|
||||
#include "mitsubishi_v1.h"
|
||||
#include "honda.h"
|
||||
#include "citroen.h"
|
||||
#include "peugeot.h"
|
||||
#include "mazda_siemens.h"
|
||||
#include "keys.h"
|
||||
|
||||
Reference in New Issue
Block a user