From 6758a4e6e386a04b8ae2bfca53d628a8906975b6 Mon Sep 17 00:00:00 2001 From: 0mega <171701900+zero-mega@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:10:50 +0200 Subject: [PATCH] Fix Mazda V0 decoder and add encoder --- protocols/mazda_v0.c | 913 +++++++++++++++++-------- protocols/mazda_v0.h | 30 +- scenes/protopirate_scene_emulate.c | 15 + scenes/protopirate_scene_need_saving.c | 2 + 4 files changed, 681 insertions(+), 279 deletions(-) diff --git a/protocols/mazda_v0.c b/protocols/mazda_v0.c index 6e41be4..15c841f 100644 --- a/protocols/mazda_v0.c +++ b/protocols/mazda_v0.c @@ -1,350 +1,727 @@ #include "mazda_v0.h" + #include -// Original implementation by @lupettohf -#define MAZDA_PREAMBLE_MIN 13 -#define MAZDA_COMPLETION_MIN 80 -#define MAZDA_COMPLETION_MAX 105 -#define MAZDA_DATA_BUFFER_SIZE 14 +// ============================================================================= +// PROTOCOL CONSTANTS +// ============================================================================= -static const SubGhzBlockConst subghz_protocol_mazda_const = { +static const SubGhzBlockConst subghz_protocol_mazda_v0_const = { .te_short = 250, .te_long = 500, .te_delta = 100, .min_count_bit_for_found = 64, }; -typedef enum { - MazdaDecoderStepReset = 0, - MazdaDecoderStepPreambleSave, - MazdaDecoderStepPreambleCheck, - MazdaDecoderStepDataSave, - MazdaDecoderStepDataCheck, -} MazdaDecoderStep; +#define MAZDA_V0_UPLOAD_CAPACITY 0x184 +#define MAZDA_V0_GAP_US 0xCB20 +#define MAZDA_V0_SYNC_BYTE 0xD7 +#define MAZDA_V0_TAIL_BYTE 0x5A +#define MAZDA_V0_PREAMBLE_ONES 16 -struct SubGhzProtocolDecoderMazda { +// ============================================================================= +// STRUCT DEFINITIONS +// ============================================================================= + +typedef struct SubGhzProtocolDecoderMazdaV0 { SubGhzProtocolDecoderBase base; SubGhzBlockDecoder decoder; SubGhzBlockGeneric generic; + ManchesterState manchester_state; uint16_t preamble_count; - uint16_t bit_counter; - uint8_t prev_state; - uint8_t data_buffer[MAZDA_DATA_BUFFER_SIZE]; + uint8_t preamble_pattern; + + uint32_t serial; + uint8_t button; + uint32_t count; +} SubGhzProtocolDecoderMazdaV0; + +#ifdef ENABLE_EMULATE_FEATURE +typedef struct SubGhzProtocolEncoderMazdaV0 { + SubGhzProtocolEncoderBase base; + SubGhzProtocolBlockEncoder encoder; + SubGhzBlockGeneric generic; + + uint32_t serial; + uint8_t button; + uint32_t count; +} SubGhzProtocolEncoderMazdaV0; +#endif + +typedef enum { + MazdaV0DecoderStepReset = 0, + MazdaV0DecoderStepPreamble = 5, + MazdaV0DecoderStepData = 6, +} MazdaV0DecoderStep; + +// ============================================================================= +// FUNCTION PROTOTYPES +// ============================================================================= + +static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event); +static void mazda_v0_decode_key(SubGhzBlockGeneric* generic); +#ifdef ENABLE_EMULATE_FEATURE +static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter); +static bool mazda_v0_encoder_add_level( + SubGhzProtocolEncoderMazdaV0* instance, + size_t* index, + bool level, + uint32_t duration); +static bool mazda_v0_append_byte( + SubGhzProtocolEncoderMazdaV0* instance, + size_t* index, + uint8_t value); +static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance); +#endif +static SubGhzProtocolStatus + mazda_v0_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button); + +// ============================================================================= +// PROTOCOL INTERFACE DEFINITIONS +// ============================================================================= + +const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = { + .alloc = subghz_protocol_decoder_mazda_v0_alloc, + .free = subghz_protocol_decoder_mazda_v0_free, + .feed = subghz_protocol_decoder_mazda_v0_feed, + .reset = subghz_protocol_decoder_mazda_v0_reset, + .get_hash_data = subghz_protocol_decoder_mazda_v0_get_hash_data, + .serialize = subghz_protocol_decoder_mazda_v0_serialize, + .deserialize = subghz_protocol_decoder_mazda_v0_deserialize, + .get_string = subghz_protocol_decoder_mazda_v0_get_string, }; -// ============================================================================ -// Helpers -// ============================================================================ - -static uint8_t mazda_byte_parity(uint8_t value) { - value ^= value >> 4; - value ^= value >> 2; - value ^= value >> 1; - return value & 1; -} - -static void mazda_xor_deobfuscate(uint8_t* data) { - uint8_t parity = mazda_byte_parity(data[7]); - - if(parity) { - uint8_t mask = data[6]; - for(uint8_t i = 0; i < 6; i++) { - data[i] ^= mask; - } - } else { - uint8_t mask = data[5]; - for(uint8_t i = 0; i < 5; i++) { - data[i] ^= mask; - } - data[6] ^= mask; - } - - uint8_t old5 = data[5]; - uint8_t old6 = data[6]; - data[5] = (old5 & 0xAAU) | (old6 & 0x55U); - data[6] = (old5 & 0x55U) | (old6 & 0xAAU); -} - -static void mazda_parse_data(SubGhzBlockGeneric* generic) { - generic->serial = (uint32_t)(generic->data >> 32); - generic->btn = (generic->data >> 24) & 0xFF; - generic->cnt = (generic->data >> 8) & 0xFFFF; -} - -static const char* mazda_get_btn_name(uint8_t btn) { - switch(btn) { - case 0x10: - return "Lock"; - case 0x20: - return "Unlock"; - case 0x40: - return "Trunk"; - default: - return "Unknown"; - } -} - -static inline bool mazda_is_short(uint32_t duration) { - return DURATION_DIFF(duration, subghz_protocol_mazda_const.te_short) < - subghz_protocol_mazda_const.te_delta; -} - -static inline bool mazda_is_long(uint32_t duration) { - return DURATION_DIFF(duration, subghz_protocol_mazda_const.te_long) < - subghz_protocol_mazda_const.te_delta; -} - -static void mazda_collect_bit(SubGhzProtocolDecoderMazda* instance, uint8_t state_bit) { - uint8_t byte_idx = instance->bit_counter >> 3; - if(byte_idx < MAZDA_DATA_BUFFER_SIZE) { - instance->data_buffer[byte_idx] <<= 1; - if(state_bit == 0) { - instance->data_buffer[byte_idx] |= 1; - } - } - instance->bit_counter++; -} - -static bool mazda_check_completion(SubGhzProtocolDecoderMazda* instance) { - if(instance->bit_counter < MAZDA_COMPLETION_MIN || instance->bit_counter > MAZDA_COMPLETION_MAX) { - return false; - } - - // Shift buffer by 1 byte (discard sync/header byte) - uint8_t data[8]; - for(uint8_t i = 0; i < 8; i++) { - data[i] = instance->data_buffer[i + 1]; - } - - mazda_xor_deobfuscate(data); - - uint8_t checksum = 0; - for(uint8_t i = 0; i < 7; i++) { - checksum += data[i]; - } - if(checksum != data[7]) { - return false; - } - - uint64_t packed = 0; - for(uint8_t i = 0; i < 8; i++) { - packed = (packed << 8) | data[i]; - } - - instance->generic.data = packed; - instance->generic.data_count_bit = 64; - mazda_parse_data(&instance->generic); - return true; -} - -static bool - mazda_process_pair(SubGhzProtocolDecoderMazda* instance, uint32_t dur_first, uint32_t dur_second) { - bool first_short = mazda_is_short(dur_first); - bool first_long = mazda_is_long(dur_first); - bool second_short = mazda_is_short(dur_second); - bool second_long = mazda_is_long(dur_second); - - if(first_long && second_short) { - mazda_collect_bit(instance, 0); - mazda_collect_bit(instance, 1); - instance->prev_state = 1; - return true; - } - - if(first_short && second_long) { - mazda_collect_bit(instance, 1); - instance->prev_state = 0; - return true; - } - - if(first_short && second_short) { - mazda_collect_bit(instance, instance->prev_state); - return true; - } - - if(first_long && second_long) { - mazda_collect_bit(instance, 0); - mazda_collect_bit(instance, 1); - instance->prev_state = 0; - return true; - } - - return false; -} - -const SubGhzProtocolDecoder subghz_protocol_mazda_decoder = { - .alloc = subghz_protocol_decoder_mazda_alloc, - .free = subghz_protocol_decoder_mazda_free, - .feed = subghz_protocol_decoder_mazda_feed, - .reset = subghz_protocol_decoder_mazda_reset, - .get_hash_data = subghz_protocol_decoder_mazda_get_hash_data, - .serialize = subghz_protocol_decoder_mazda_serialize, - .deserialize = subghz_protocol_decoder_mazda_deserialize, - .get_string = subghz_protocol_decoder_mazda_get_string, +#ifdef ENABLE_EMULATE_FEATURE +const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = { + .alloc = subghz_protocol_encoder_mazda_v0_alloc, + .free = subghz_protocol_encoder_mazda_v0_free, + .deserialize = subghz_protocol_encoder_mazda_v0_deserialize, + .stop = subghz_protocol_encoder_mazda_v0_stop, + .yield = subghz_protocol_encoder_mazda_v0_yield, }; - -const SubGhzProtocolEncoder subghz_protocol_mazda_encoder = { +#else +const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = { .alloc = NULL, .free = NULL, .deserialize = NULL, .stop = NULL, .yield = NULL, }; +#endif const SubGhzProtocol mazda_v0_protocol = { - .name = MAZDA_PROTOCOL_NAME, - .type = SubGhzProtocolTypeStatic, + .name = MAZDA_PROTOCOL_V0_NAME, + .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, - .decoder = &subghz_protocol_mazda_decoder, - .encoder = &subghz_protocol_mazda_encoder, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + .decoder = &subghz_protocol_mazda_v0_decoder, + .encoder = &subghz_protocol_mazda_v0_encoder, }; -// ============================================================================ -// Decoder -// ============================================================================ +// ============================================================================= +// HELPERS +// ============================================================================= -void* subghz_protocol_decoder_mazda_alloc(SubGhzEnvironment* environment) { - UNUSED(environment); - SubGhzProtocolDecoderMazda* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazda)); +static uint8_t mazda_v0_popcount8(uint8_t x) { + uint8_t count = 0; + while(x) { + count += x & 1; + x >>= 1; + } + return count; +} + +static void mazda_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) { + for(size_t i = 0; i < 8; i++) { + bytes[i] = (uint8_t)((data >> ((7 - i) * 8)) & 0xFF); + } +} + +static uint64_t mazda_v0_bytes_to_u64_be(const uint8_t bytes[8]) { + uint64_t data = 0; + for(size_t i = 0; i < 8; i++) { + data = (data << 8) | bytes[i]; + } + return data; +} + +static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) { + counter &= 0xFFFFFU; + return (uint8_t)( + ((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) + ((serial >> 8) & 0xFF) + + (serial & 0xFF) + ((counter >> 8) & 0xFF) + (counter & 0xFF) + + ((((counter >> 16) & 0x0F) | ((button & 0x0F) << 4)) & 0xFF)); +} + +static const char* mazda_v0_get_button_name(uint8_t button) { + switch(button) { + case 0x01: + return "Lock"; + case 0x02: + return "Unlock"; + case 0x04: + return "Trunk"; + case 0x08: + return "Remote"; + default: + return "??"; + } +} + +static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event) { + const uint32_t tol = (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U; + + if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < tol) { + *event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; + return true; + } + + if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_long) < tol) { + *event = level ? ManchesterEventLongLow : ManchesterEventLongHigh; + return true; + } + + return false; +} + +static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) { + uint8_t data[8]; + mazda_v0_u64_to_bytes_be(generic->data, data); + + const bool parity = (mazda_v0_popcount8(data[7]) & 1) != 0; + const uint8_t limit = parity ? 6 : 5; + const uint8_t mask = data[limit]; + + for(uint8_t i = 0; i < limit; i++) { + data[i] ^= mask; + } + + if(!parity) { + data[6] ^= mask; + } + + const uint8_t counter_lo = (data[5] & 0x55) | (data[6] & 0xAA); + const uint8_t counter_mid = (data[6] & 0x55) | (data[5] & 0xAA); + + generic->serial = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | + ((uint32_t)data[2] << 8) | (uint32_t)data[3]; + generic->btn = (data[4] >> 4) & 0x0F; + generic->cnt = (((uint32_t)data[4] & 0x0F) << 16) | ((uint32_t)counter_mid << 8) | + (uint32_t)counter_lo; + generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found; +} + +#ifdef ENABLE_EMULATE_FEATURE +static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) { + uint8_t data[8]; + + counter &= 0xFFFFFU; + button &= 0x0F; + + data[0] = (serial >> 24) & 0xFF; + data[1] = (serial >> 16) & 0xFF; + data[2] = (serial >> 8) & 0xFF; + data[3] = serial & 0xFF; + data[4] = (button << 4) | ((counter >> 16) & 0x0F); + data[5] = (counter >> 8) & 0xFF; + data[6] = counter & 0xFF; + data[7] = mazda_v0_calculate_checksum(serial, button, counter); + + const uint8_t stored_5 = (data[6] & 0x55) | (data[5] & 0xAA); + const uint8_t stored_6 = (data[6] & 0xAA) | (data[5] & 0x55); + const uint8_t xor_mask = stored_5 ^ stored_6; + const bool replace_second = ((~mazda_v0_popcount8(data[7])) & 1) != 0; + const uint8_t forward_mask = replace_second ? stored_5 : stored_6; + + data[5] = replace_second ? stored_5 : xor_mask; + data[6] = replace_second ? xor_mask : stored_6; + + for(size_t i = 0; i < 5; i++) { + data[i] ^= forward_mask; + } + + return mazda_v0_bytes_to_u64_be(data); +} + +static bool mazda_v0_encoder_add_level( + SubGhzProtocolEncoderMazdaV0* instance, + size_t* index, + bool level, + uint32_t duration) { + if(*index >= MAZDA_V0_UPLOAD_CAPACITY) { + return false; + } + instance->encoder.upload[(*index)++] = level_duration_make(level, duration); + return true; +} + +static bool mazda_v0_append_byte( + SubGhzProtocolEncoderMazdaV0* instance, + size_t* index, + uint8_t value) { + if(*index + 16 > MAZDA_V0_UPLOAD_CAPACITY) { + return false; + } + + const uint32_t te = subghz_protocol_mazda_v0_const.te_short; + + for(int8_t bit = 7; bit >= 0; bit--) { + const bool bit_value = ((value >> bit) & 1) != 0; + if(!bit_value) { + if(!mazda_v0_encoder_add_level(instance, index, false, te)) { + return false; + } + if(!mazda_v0_encoder_add_level(instance, index, true, te)) { + return false; + } + } else { + if(!mazda_v0_encoder_add_level(instance, index, true, te)) { + return false; + } + if(!mazda_v0_encoder_add_level(instance, index, false, te)) { + return false; + } + } + } + + return true; +} + +static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) { furi_check(instance); + + size_t index = 0; + const uint64_t key64 = instance->generic.data; + + for(size_t r = 0; r < 12; r++) { + if(!mazda_v0_append_byte(instance, &index, 0xFF)) { + return false; + } + } + + if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) { + return false; + } + + if(!mazda_v0_append_byte(instance, &index, 0xFF) || + !mazda_v0_append_byte(instance, &index, 0xFF) || + !mazda_v0_append_byte(instance, &index, MAZDA_V0_SYNC_BYTE)) { + return false; + } + + for(int bi = 0; bi < 8; bi++) { + const uint8_t raw = (uint8_t)((key64 >> (56 - bi * 8)) & 0xFF); + const uint8_t air = (uint8_t)~raw; + if(!mazda_v0_append_byte(instance, &index, air)) { + return false; + } + } + + if(!mazda_v0_append_byte(instance, &index, MAZDA_V0_TAIL_BYTE)) { + return false; + } + + if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) { + return false; + } + + instance->encoder.front = 0; + instance->encoder.size_upload = index; + + return true; +} +#endif + +static SubGhzProtocolStatus + mazda_v0_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) { + SubGhzProtocolStatus status = SubGhzProtocolStatusOk; + FuriString* display = furi_string_alloc(); + + furi_string_printf( + display, "%s - %s", protocol_name, mazda_v0_get_button_name(button)); + + if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) { + status = SubGhzProtocolStatusErrorParserOthers; + } + + furi_string_free(display); + return status; +} + +// ============================================================================= +// ENCODER +// ============================================================================= + +#ifdef ENABLE_EMULATE_FEATURE +void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + + SubGhzProtocolEncoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMazdaV0)); + furi_check(instance); + instance->base.protocol = &mazda_v0_protocol; instance->generic.protocol_name = instance->base.protocol->name; + instance->encoder.repeat = 10; + instance->encoder.size_upload = 0; + instance->encoder.front = 0; + instance->encoder.is_running = false; + instance->encoder.upload = malloc(MAZDA_V0_UPLOAD_CAPACITY * sizeof(LevelDuration)); + furi_check(instance->encoder.upload); + return instance; } -void subghz_protocol_decoder_mazda_free(void* context) { +void subghz_protocol_encoder_mazda_v0_free(void* context) { furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; + SubGhzProtocolEncoderMazdaV0* instance = context; + free(instance->encoder.upload); free(instance); } -void subghz_protocol_decoder_mazda_reset(void* context) { +SubGhzProtocolStatus + subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) { furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; - instance->decoder.parser_step = MazdaDecoderStepReset; - instance->preamble_count = 0; - instance->bit_counter = 0; - instance->prev_state = 0; - instance->generic.data = 0; - instance->generic.data_count_bit = 0; - memset(instance->data_buffer, 0, sizeof(instance->data_buffer)); + SubGhzProtocolEncoderMazdaV0* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + + instance->encoder.is_running = false; + instance->encoder.front = 0; + instance->encoder.repeat = 10; + + do { + FuriString* temp_str = furi_string_alloc(); + if(!temp_str) { + break; + } + + flipper_format_rewind(flipper_format); + if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) { + furi_string_free(temp_str); + break; + } + + if(!furi_string_equal(temp_str, instance->base.protocol->name)) { + furi_string_free(temp_str); + break; + } + furi_string_free(temp_str); + + flipper_format_rewind(flipper_format); + SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mazda_v0_const.min_count_bit_for_found); + if(load_st != SubGhzProtocolStatusOk) { + break; + } + + mazda_v0_decode_key(&instance->generic); + + uint32_t u32 = 0; + + flipper_format_rewind(flipper_format); + if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) { + instance->generic.serial = u32; + } + flipper_format_rewind(flipper_format); + if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) { + instance->generic.btn = (uint8_t)u32; + } + flipper_format_rewind(flipper_format); + if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) { + instance->generic.cnt = u32; + } + + instance->serial = instance->generic.serial; + instance->button = instance->generic.btn; + instance->count = instance->generic.cnt; + + flipper_format_rewind(flipper_format); + if(!flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) { + instance->encoder.repeat = 10; + } + instance->generic.btn &= 0x0FU; + instance->generic.cnt &= 0xFFFFFU; + + instance->generic.data = mazda_v0_encode_key( + instance->generic.serial, instance->generic.btn, instance->generic.cnt); + instance->generic.data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found; + + instance->serial = instance->generic.serial; + instance->button = instance->generic.btn; + instance->count = instance->generic.cnt; + + if(!mazda_v0_build_upload(instance)) { + break; + } + + if(instance->encoder.size_upload == 0) { + break; + } + + flipper_format_rewind(flipper_format); + uint8_t key_data[sizeof(uint64_t)]; + mazda_v0_u64_to_bytes_be(instance->generic.data, key_data); + if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) { + break; + } + + uint32_t chk = mazda_v0_calculate_checksum( + instance->serial, instance->button, instance->count); + flipper_format_rewind(flipper_format); + flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &chk, 1); + + instance->encoder.is_running = true; + + ret = SubGhzProtocolStatusOk; + } while(false); + + return ret; } -void subghz_protocol_decoder_mazda_feed(void* context, bool level, uint32_t duration) { +void subghz_protocol_encoder_mazda_v0_stop(void* context) { furi_check(context); - UNUSED(level); - SubGhzProtocolDecoderMazda* instance = context; + SubGhzProtocolEncoderMazdaV0* instance = context; + instance->encoder.is_running = false; +} + +LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context) { + furi_check(context); + SubGhzProtocolEncoderMazdaV0* instance = context; + + if(!instance->encoder.is_running || instance->encoder.repeat == 0) { + instance->encoder.is_running = false; + return level_duration_reset(); + } + + LevelDuration out = instance->encoder.upload[instance->encoder.front]; + + if(++instance->encoder.front == instance->encoder.size_upload) { + instance->encoder.repeat--; + instance->encoder.front = 0; + } + + return out; +} +#endif + +// ============================================================================= +// DECODER +// ============================================================================= + +void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + + SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0)); + furi_check(instance); + + instance->base.protocol = &mazda_v0_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + + return instance; +} + +void subghz_protocol_decoder_mazda_v0_free(void* context) { + furi_check(context); + SubGhzProtocolDecoderMazdaV0* instance = context; + free(instance); +} + +void subghz_protocol_decoder_mazda_v0_reset(void* context) { + furi_check(context); + SubGhzProtocolDecoderMazdaV0* instance = context; + + instance->decoder.parser_step = MazdaV0DecoderStepReset; + instance->decoder.te_last = 0; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->manchester_state = ManchesterStateStart1; + instance->preamble_count = 0; + instance->preamble_pattern = 0; +} + +void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + + SubGhzProtocolDecoderMazdaV0* instance = context; + ManchesterEvent event = ManchesterEventReset; + bool data = false; switch(instance->decoder.parser_step) { - case MazdaDecoderStepReset: - if(mazda_is_short(duration)) { - instance->decoder.te_last = duration; + case MazdaV0DecoderStepReset: + if(level && + ((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < + (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = MazdaV0DecoderStepPreamble; + instance->manchester_state = ManchesterStateMid1; instance->preamble_count = 0; - instance->decoder.parser_step = MazdaDecoderStepPreambleCheck; + instance->preamble_pattern = 0; } break; - case MazdaDecoderStepPreambleSave: - instance->decoder.te_last = duration; - instance->decoder.parser_step = MazdaDecoderStepPreambleCheck; - break; - - case MazdaDecoderStepPreambleCheck: - if(mazda_is_short(instance->decoder.te_last) && mazda_is_short(duration)) { - instance->preamble_count++; - instance->decoder.parser_step = MazdaDecoderStepPreambleSave; - } else if( - mazda_is_short(instance->decoder.te_last) && mazda_is_long(duration) && - instance->preamble_count >= MAZDA_PREAMBLE_MIN) { - instance->bit_counter = 1; - memset(instance->data_buffer, 0, sizeof(instance->data_buffer)); - mazda_collect_bit(instance, 1); - instance->prev_state = 0; - instance->decoder.parser_step = MazdaDecoderStepDataSave; - } else { - instance->decoder.parser_step = MazdaDecoderStepReset; + case MazdaV0DecoderStepPreamble: + if(!mazda_v0_get_event(duration, level, &event)) { + instance->decoder.parser_step = MazdaV0DecoderStepReset; + break; } - break; - case MazdaDecoderStepDataSave: - instance->decoder.te_last = duration; - instance->decoder.parser_step = MazdaDecoderStepDataCheck; - break; + if(manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data)) { + instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0); - case MazdaDecoderStepDataCheck: - if(mazda_process_pair(instance, instance->decoder.te_last, duration)) { - instance->decoder.parser_step = MazdaDecoderStepDataSave; - } else { - if(mazda_check_completion(instance) && instance->base.callback) { - instance->base.callback(&instance->base, instance->base.context); + if(data) { + instance->preamble_count++; + } else if(instance->preamble_count <= MAZDA_V0_PREAMBLE_ONES - 1U) { + instance->preamble_count = 0; + instance->preamble_pattern = 0; + break; + } + + if((instance->preamble_pattern == MAZDA_V0_SYNC_BYTE) && + (instance->preamble_count > MAZDA_V0_PREAMBLE_ONES - 1U)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = MazdaV0DecoderStepData; + } + } + break; + + case MazdaV0DecoderStepData: + if(!mazda_v0_get_event(duration, level, &event)) { + instance->decoder.parser_step = MazdaV0DecoderStepReset; + break; + } + + if(manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data)) { + subghz_protocol_blocks_add_bit(&instance->decoder, data); + + if(instance->decoder.decode_count_bit == + subghz_protocol_mazda_v0_const.min_count_bit_for_found) { + instance->generic.data = ~instance->decoder.decode_data; + mazda_v0_decode_key(&instance->generic); + + if(mazda_v0_calculate_checksum( + instance->generic.serial, instance->generic.btn, instance->generic.cnt) == + (uint8_t)instance->generic.data) { + instance->serial = instance->generic.serial; + instance->button = instance->generic.btn; + instance->count = instance->generic.cnt; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->preamble_count = 0; + instance->preamble_pattern = 0; + instance->manchester_state = ManchesterStateStart1; + instance->decoder.te_last = 0; + instance->decoder.parser_step = MazdaV0DecoderStepReset; } - instance->decoder.parser_step = MazdaDecoderStepReset; } break; } } -uint8_t subghz_protocol_decoder_mazda_get_hash_data(void* context) { +uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context) { furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; - SubGhzBlockDecoder decoder = { - .decode_data = instance->generic.data, - .decode_count_bit = instance->generic.data_count_bit, - }; - return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1); + SubGhzProtocolDecoderMazdaV0* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } -SubGhzProtocolStatus subghz_protocol_decoder_mazda_serialize( +SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset) { furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; - return subghz_block_generic_serialize(&instance->generic, flipper_format, preset); -} + SubGhzProtocolDecoderMazdaV0* instance = context; + + mazda_v0_decode_key(&instance->generic); + instance->serial = instance->generic.serial; + instance->button = instance->generic.btn; + instance->count = instance->generic.cnt; + + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); -SubGhzProtocolStatus - subghz_protocol_decoder_mazda_deserialize(void* context, FlipperFormat* flipper_format) { - furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; - SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( - &instance->generic, flipper_format, subghz_protocol_mazda_const.min_count_bit_for_found); if(ret == SubGhzProtocolStatusOk) { - mazda_parse_data(&instance->generic); + uint32_t chk = mazda_v0_calculate_checksum( + instance->serial, instance->button, instance->count); + flipper_format_write_uint32(flipper_format, "Checksum", &chk, 1); + + flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1); + + uint32_t temp = instance->button; + flipper_format_write_uint32(flipper_format, "Btn", &temp, 1); + + flipper_format_write_uint32(flipper_format, "Cnt", &instance->count, 1); + + ret = mazda_v0_write_display( + flipper_format, instance->generic.protocol_name, instance->button); } + return ret; } -void subghz_protocol_decoder_mazda_get_string(void* context, FuriString* output) { +SubGhzProtocolStatus + subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) { furi_check(context); - SubGhzProtocolDecoderMazda* instance = context; - mazda_parse_data(&instance->generic); + SubGhzProtocolDecoderMazdaV0* instance = context; - uint8_t data[8]; - for(uint8_t i = 0; i < 8; i++) { - data[i] = (instance->generic.data >> (56 - 8 * i)) & 0xFF; + SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mazda_v0_const.min_count_bit_for_found); + + if(ret == SubGhzProtocolStatusOk) { + flipper_format_rewind(flipper_format); + + flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1); + instance->generic.serial = instance->serial; + + uint32_t btn_temp = 0; + flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1); + instance->button = (uint8_t)btn_temp; + instance->generic.btn = instance->button; + + flipper_format_read_uint32(flipper_format, "Cnt", &instance->count, 1); + instance->generic.cnt = instance->count; } + return ret; +} + +void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderMazdaV0* instance = context; + + mazda_v0_decode_key(&instance->generic); + + const uint8_t raw_crc = instance->generic.data & 0xFF; + const uint8_t calc_crc = mazda_v0_calculate_checksum( + instance->generic.serial, instance->generic.btn, instance->generic.cnt); + furi_string_cat_printf( output, - "%s %dbit\r\n" - "Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n" - "Sn:%08lX Btn:%s\r\n" - "Cnt:%04lX Chk:%02X\r\n", + "%s %dbit CRC:%s\r\n" + "Key: %016llX\r\n" + "Sn: %08lX Btn: %02X - %s\r\n" + "Cnt: %05lX Chk: %02X\r\n", instance->generic.protocol_name, instance->generic.data_count_bit, - data[0], - data[1], - data[2], - data[3], - data[4], - data[5], - data[6], - data[7], - (uint32_t)instance->generic.serial, - mazda_get_btn_name(instance->generic.btn), - (uint32_t)instance->generic.cnt, - data[7]); + (raw_crc == calc_crc) ? "OK" : "BAD", + (unsigned long long)instance->generic.data, + (unsigned long)instance->generic.serial, + instance->generic.btn, + mazda_v0_get_button_name(instance->generic.btn), + (unsigned long)(instance->generic.cnt & 0xFFFFFU), + raw_crc); } diff --git a/protocols/mazda_v0.h b/protocols/mazda_v0.h index f71e508..0fc42b8 100644 --- a/protocols/mazda_v0.h +++ b/protocols/mazda_v0.h @@ -5,27 +5,35 @@ #include #include #include +#include #include #include #include +#include +#include #include "../defines.h" -#define MAZDA_PROTOCOL_NAME "Mazda V0" - -typedef struct SubGhzProtocolDecoderMazda SubGhzProtocolDecoderMazda; +#define MAZDA_PROTOCOL_V0_NAME "Mazda V0" extern const SubGhzProtocol mazda_v0_protocol; -void* subghz_protocol_decoder_mazda_alloc(SubGhzEnvironment* environment); -void subghz_protocol_decoder_mazda_free(void* context); -void subghz_protocol_decoder_mazda_reset(void* context); -void subghz_protocol_decoder_mazda_feed(void* context, bool level, uint32_t duration); -uint8_t subghz_protocol_decoder_mazda_get_hash_data(void* context); -SubGhzProtocolStatus subghz_protocol_decoder_mazda_serialize( +void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_mazda_v0_free(void* context); +void subghz_protocol_decoder_mazda_v0_reset(void* context); +void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context); +SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize( void* context, FlipperFormat* flipper_format, SubGhzRadioPreset* preset); SubGhzProtocolStatus - subghz_protocol_decoder_mazda_deserialize(void* context, FlipperFormat* flipper_format); -void subghz_protocol_decoder_mazda_get_string(void* context, FuriString* output); + subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output); + +void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment); +void subghz_protocol_encoder_mazda_v0_free(void* context); +SubGhzProtocolStatus + subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_encoder_mazda_v0_stop(void* context); +LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context); diff --git a/scenes/protopirate_scene_emulate.c b/scenes/protopirate_scene_emulate.c index d055c5c..255c830 100644 --- a/scenes/protopirate_scene_emulate.c +++ b/scenes/protopirate_scene_emulate.c @@ -165,6 +165,21 @@ static uint8_t return original; } } + // Mazda V0 + else if(strstr(protocol, "Mazda")) { + switch(key) { + case InputKeyUp: + return 0x01; // Lock + case InputKeyOk: + return 0x02; // Unlock + case InputKeyDown: + return 0x04; // Trunk + case InputKeyRight: + return 0x08; // Remote + default: + return original; + } + } // Ford - (needs testing) else if(strstr(protocol, "Ford")) { switch(key) { diff --git a/scenes/protopirate_scene_need_saving.c b/scenes/protopirate_scene_need_saving.c index 8bcbc35..07d6b4c 100644 --- a/scenes/protopirate_scene_need_saving.c +++ b/scenes/protopirate_scene_need_saving.c @@ -2,6 +2,8 @@ #include "../protopirate_app_i.h" #include "proto_pirate_icons.h" +#define TAG "ProtoPirateNeedSaving" + static void protopirate_scene_need_saving_callback(GuiButtonType result, InputType type, void* context) { furi_assert(context);