Compare commits

...

1 Commits

Author SHA1 Message Date
d4rks1d33
8bf12df45d Added untested new protocols 2026-03-15 19:26:40 -03:00
12 changed files with 1745 additions and 0 deletions

296
lib/subghz/protocols/bmw.c Normal file
View 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);
}

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

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

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

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

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

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

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

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

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

View File

@@ -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 = {

View File

@@ -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"