diff --git a/lib/subghz/protocols/bmw.c b/lib/subghz/protocols/bmw.c new file mode 100644 index 0000000..969e6ae --- /dev/null +++ b/lib/subghz/protocols/bmw.c @@ -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); +} diff --git a/lib/subghz/protocols/bmw.h b/lib/subghz/protocols/bmw.h new file mode 100644 index 0000000..a636ddb --- /dev/null +++ b/lib/subghz/protocols/bmw.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/lib/subghz/protocols/citroen.c b/lib/subghz/protocols/citroen.c new file mode 100644 index 0000000..0793553 --- /dev/null +++ b/lib/subghz/protocols/citroen.c @@ -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); +} diff --git a/lib/subghz/protocols/citroen.h b/lib/subghz/protocols/citroen.h new file mode 100644 index 0000000..c874063 --- /dev/null +++ b/lib/subghz/protocols/citroen.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#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); diff --git a/lib/subghz/protocols/honda.c b/lib/subghz/protocols/honda.c new file mode 100644 index 0000000..3c4ebe1 --- /dev/null +++ b/lib/subghz/protocols/honda.c @@ -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); +} diff --git a/lib/subghz/protocols/honda.h b/lib/subghz/protocols/honda.h new file mode 100644 index 0000000..01fc85e --- /dev/null +++ b/lib/subghz/protocols/honda.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#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); diff --git a/lib/subghz/protocols/mitsubishi_v1.c b/lib/subghz/protocols/mitsubishi_v1.c new file mode 100644 index 0000000..1310dba --- /dev/null +++ b/lib/subghz/protocols/mitsubishi_v1.c @@ -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); +} diff --git a/lib/subghz/protocols/mitsubishi_v1.h b/lib/subghz/protocols/mitsubishi_v1.h new file mode 100644 index 0000000..59ef988 --- /dev/null +++ b/lib/subghz/protocols/mitsubishi_v1.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#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); diff --git a/lib/subghz/protocols/peugeot.c b/lib/subghz/protocols/peugeot.c new file mode 100644 index 0000000..394cafb --- /dev/null +++ b/lib/subghz/protocols/peugeot.c @@ -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); +} diff --git a/lib/subghz/protocols/peugeot.h b/lib/subghz/protocols/peugeot.h new file mode 100644 index 0000000..1bab368 --- /dev/null +++ b/lib/subghz/protocols/peugeot.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#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); diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index df2e0e2..f39b5db 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -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 = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index c027146..7db3879 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -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"