From 9ac1ea4ce3d2b9a3e88d590ca74bca375816a46f Mon Sep 17 00:00:00 2001 From: 0mega <171701900+zero-mega@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:16:12 +0100 Subject: [PATCH] Add Fiat V1, Mazda V0, Mitsubishi V0 and Porsche/Touareg Ported from ARF Original implementation by @lupettohf --- protocols/fiat_v1.c | 440 ++++++++++++++++++++++++++++++++++++ protocols/fiat_v1.h | 32 +++ protocols/mazda_v0.c | 350 ++++++++++++++++++++++++++++ protocols/mazda_v0.h | 31 +++ protocols/mitsubishi_v0.c | 261 +++++++++++++++++++++ protocols/mitsubishi_v0.h | 31 +++ protocols/porsche_touareg.c | 372 ++++++++++++++++++++++++++++++ protocols/porsche_touareg.h | 32 +++ protocols/protocol_items.c | 36 +++ protocols/protocol_items.h | 4 + 10 files changed, 1589 insertions(+) create mode 100644 protocols/fiat_v1.c create mode 100644 protocols/fiat_v1.h create mode 100644 protocols/mazda_v0.c create mode 100644 protocols/mazda_v0.h create mode 100644 protocols/mitsubishi_v0.c create mode 100644 protocols/mitsubishi_v0.h create mode 100644 protocols/porsche_touareg.c create mode 100644 protocols/porsche_touareg.h diff --git a/protocols/fiat_v1.c b/protocols/fiat_v1.c new file mode 100644 index 0000000..4d588b7 --- /dev/null +++ b/protocols/fiat_v1.c @@ -0,0 +1,440 @@ +#include "fiat_v1.h" +#include + +// Magneti Marelli BSI keyfob protocol (PCF7946) +// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012) +// +// RF: 433.92 MHz, Manchester encoding +// Two timing variants with identical frame structure: +// Type A (e.g. Panda): te_short ~260us, te_long ~520us +// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us +// TE is auto-detected from preamble pulse averaging. +// +// Frame layout (103-104 bits = 13 bytes): +// Bytes 0-1: 0xFFFF/0xFFFC preamble residue +// Bytes 2-5: Serial (32 bits) +// Byte 6: [Button:4 | Epoch:4] +// Byte 7: [Counter:5 | Scramble:2 | Fixed:1] +// Bytes 8-12: Encrypted payload (40 bits) +// +// Original implementation by @lupettohf + +#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 50 +#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 350 +#define FIAT_MARELLI_PREAMBLE_MIN 80 +#define FIAT_MARELLI_MAX_DATA_BITS 104 +#define FIAT_MARELLI_MIN_DATA_BITS 80 +#define FIAT_MARELLI_GAP_TE_MULT 4 +#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4 +#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12 +#define FIAT_MARELLI_RETX_GAP_MIN 5000 +#define FIAT_MARELLI_RETX_SYNC_MIN 400 +#define FIAT_MARELLI_RETX_SYNC_MAX 2800 +#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180 + +static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = { + .te_short = 260, + .te_long = 520, + .te_delta = 80, + .min_count_bit_for_found = FIAT_MARELLI_MIN_DATA_BITS, +}; + +typedef enum { + FiatMarelliDecoderStepReset = 0, + FiatMarelliDecoderStepPreamble = 1, + FiatMarelliDecoderStepSync = 2, + FiatMarelliDecoderStepData = 3, + FiatMarelliDecoderStepRetxSync = 4, +} FiatMarelliDecoderStep; + +struct SubGhzProtocolDecoderFiatMarelli { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + ManchesterState manchester_state; + uint8_t decoder_state; + uint16_t preamble_count; + + uint8_t raw_data[13]; + uint8_t bit_count; + uint32_t extra_data; + + uint32_t te_last; + uint32_t te_sum; + uint16_t te_count; + uint32_t te_detected; +}; + +static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatMarelli* instance) { + instance->bit_count = 0; + instance->extra_data = 0; + instance->generic.data = 0; + instance->generic.data_count_bit = 0; + memset(instance->raw_data, 0, sizeof(instance->raw_data)); + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->decoder_state = FiatMarelliDecoderStepData; +} + +static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatMarelli* instance) { + memset(instance->raw_data, 0, sizeof(instance->raw_data)); + + uint64_t key = instance->generic.data; + for(uint8_t i = 0; i < 8; i++) { + instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8)); + } + + uint8_t extra_bits = + (instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0; + for(uint8_t i = 0; i < extra_bits && i < 32; i++) { + uint8_t byte_idx = 8 + (i / 8); + uint8_t bit_pos = 7 - (i % 8); + if(instance->extra_data & (1UL << (extra_bits - 1 - i))) { + instance->raw_data[byte_idx] |= (1U << bit_pos); + } + } + + instance->bit_count = instance->generic.data_count_bit; + + if(instance->bit_count >= 56) { + instance->generic.serial = ((uint32_t)instance->raw_data[2] << 24) | + ((uint32_t)instance->raw_data[3] << 16) | + ((uint32_t)instance->raw_data[4] << 8) | + ((uint32_t)instance->raw_data[5]); + instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F; + instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F; + } +} + +static const char* fiat_marelli_button_name(uint8_t btn) { + switch(btn) { + case 0x7: + return "Lock"; + case 0xB: + return "Unlock"; + case 0xD: + return "Trunk"; + default: + return "Unknown"; + } +} + +const SubGhzProtocolDecoder subghz_protocol_fiat_marelli_decoder = { + .alloc = subghz_protocol_decoder_fiat_marelli_alloc, + .free = subghz_protocol_decoder_fiat_marelli_free, + .feed = subghz_protocol_decoder_fiat_marelli_feed, + .reset = subghz_protocol_decoder_fiat_marelli_reset, + .get_hash_data = subghz_protocol_decoder_fiat_marelli_get_hash_data, + .serialize = subghz_protocol_decoder_fiat_marelli_serialize, + .deserialize = subghz_protocol_decoder_fiat_marelli_deserialize, + .get_string = subghz_protocol_decoder_fiat_marelli_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_fiat_marelli_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol fiat_v1_protocol = { + .name = FIAT_MARELLI_PROTOCOL_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + .decoder = &subghz_protocol_fiat_marelli_decoder, + .encoder = &subghz_protocol_fiat_marelli_encoder, +}; + +void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderFiatMarelli* instance = + calloc(1, sizeof(SubGhzProtocolDecoderFiatMarelli)); + furi_check(instance); + instance->base.protocol = &fiat_v1_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_fiat_marelli_free(void* context) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + free(instance); +} + +void subghz_protocol_decoder_fiat_marelli_reset(void* context) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + instance->decoder_state = FiatMarelliDecoderStepReset; + instance->preamble_count = 0; + instance->bit_count = 0; + instance->extra_data = 0; + instance->te_last = 0; + instance->te_sum = 0; + instance->te_count = 0; + instance->te_detected = 0; + instance->generic.data = 0; + instance->generic.data_count_bit = 0; + memset(instance->raw_data, 0, sizeof(instance->raw_data)); + instance->manchester_state = ManchesterStateMid1; +} + +void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + + uint32_t te_short = instance->te_detected ? instance->te_detected + : (uint32_t)subghz_protocol_fiat_marelli_const.te_short; + uint32_t te_long = te_short * 2; + uint32_t te_delta = te_short / 2; + if(te_delta < 30) te_delta = 30; + uint32_t diff; + + switch(instance->decoder_state) { + case FiatMarelliDecoderStepReset: + if(level) { + if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN && + duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) { + instance->decoder_state = FiatMarelliDecoderStepPreamble; + instance->preamble_count = 1; + instance->te_sum = duration; + instance->te_count = 1; + instance->te_last = duration; + } + } else if(duration > FIAT_MARELLI_RETX_GAP_MIN && instance->te_detected) { + instance->decoder_state = FiatMarelliDecoderStepRetxSync; + instance->te_last = duration; + } + break; + + case FiatMarelliDecoderStepPreamble: + if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN && + duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) { + instance->preamble_count++; + instance->te_sum += duration; + instance->te_count++; + instance->te_last = duration; + } else if(!level) { + if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) { + instance->te_detected = instance->te_sum / instance->te_count; + uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT; + + if(duration > gap_threshold) { + instance->decoder_state = FiatMarelliDecoderStepSync; + instance->te_last = duration; + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + break; + + case FiatMarelliDecoderStepSync: { + uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT; + uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT; + + if(level && duration >= sync_min && duration <= sync_max) { + fiat_marelli_prepare_data(instance); + instance->te_last = duration; + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + break; + } + + case FiatMarelliDecoderStepRetxSync: + if(level && duration >= FIAT_MARELLI_RETX_SYNC_MIN && + duration <= FIAT_MARELLI_RETX_SYNC_MAX) { + fiat_marelli_prepare_data(instance); + instance->te_last = duration; + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + break; + + case FiatMarelliDecoderStepData: { + ManchesterEvent event = ManchesterEventReset; + bool frame_complete = false; + + diff = (duration > te_short) ? (duration - te_short) : (te_short - duration); + if(diff < te_delta) { + event = level ? ManchesterEventShortLow : ManchesterEventShortHigh; + } else { + diff = (duration > te_long) ? (duration - te_long) : (te_long - duration); + if(diff < te_delta) { + event = level ? ManchesterEventLongLow : ManchesterEventLongHigh; + } + } + + if(event != ManchesterEventReset) { + bool data_bit = false; + if(manchester_advance( + instance->manchester_state, + event, + &instance->manchester_state, + &data_bit)) { + uint32_t new_bit = data_bit ? 1U : 0U; + + if(instance->bit_count < FIAT_MARELLI_MAX_DATA_BITS) { + uint8_t byte_idx = instance->bit_count / 8; + uint8_t bit_pos = 7 - (instance->bit_count % 8); + if(new_bit) { + instance->raw_data[byte_idx] |= (1U << bit_pos); + } + } + + if(instance->bit_count < 64) { + instance->generic.data = (instance->generic.data << 1) | new_bit; + } else { + instance->extra_data = (instance->extra_data << 1) | new_bit; + } + + instance->bit_count++; + if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) { + frame_complete = true; + } + } + } else if(instance->bit_count >= FIAT_MARELLI_MIN_DATA_BITS) { + frame_complete = true; + } else { + instance->decoder_state = FiatMarelliDecoderStepReset; + } + + if(frame_complete) { + instance->generic.data_count_bit = instance->bit_count; + + instance->generic.serial = ((uint32_t)instance->raw_data[2] << 24) | + ((uint32_t)instance->raw_data[3] << 16) | + ((uint32_t)instance->raw_data[4] << 8) | + ((uint32_t)instance->raw_data[5]); + instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F; + instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder_state = FiatMarelliDecoderStepReset; + } + + instance->te_last = duration; + break; + } + } +} + +uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + SubGhzBlockDecoder decoder = { + .decode_data = instance->generic.data, + .decode_count_bit = + instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit, + }; + uint8_t hash = subghz_protocol_blocks_get_hash_data( + &decoder, (decoder.decode_count_bit / 8) + 1); + uint32_t x = instance->extra_data; + for(uint8_t i = 0; i < 4; i++) { + hash ^= (uint8_t)(x >> (i * 8)); + } + return hash; +} + +SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret == SubGhzProtocolStatusOk) { + flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1); + + uint32_t extra_bits = + (instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0; + flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1); + + uint32_t te = instance->te_detected; + flipper_format_write_uint32(flipper_format, "TE", &te, 1); + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + + SubGhzProtocolStatus ret = + subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_fiat_marelli_const.min_count_bit_for_found); + + if(ret == SubGhzProtocolStatusOk) { + uint32_t extra = 0; + if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) { + instance->extra_data = extra; + } + + uint32_t te = 0; + if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) { + instance->te_detected = te; + } + + fiat_marelli_rebuild_raw_data(instance); + } + + return ret; +} + +void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderFiatMarelli* instance = context; + + uint8_t epoch = instance->raw_data[6] & 0x0F; + uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F; + const char* variant = + (instance->te_detected && instance->te_detected < FIAT_MARELLI_TE_TYPE_AB_BOUNDARY) ? "B" : + "A"; + uint8_t scramble = (instance->raw_data[7] >> 1) & 0x03; + uint8_t fixed = instance->raw_data[7] & 0x01; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n" + "Raw:%02X%02X Fixed:%X\r\n" + "Sn:%08X Cnt:%02X\r\n" + "Btn:%02X:[%s] Ep:%02X\r\n" + "Tp:%s TE:%lu\r\n", + instance->generic.protocol_name, + (int)instance->bit_count, + instance->raw_data[8], + instance->raw_data[9], + instance->raw_data[10], + instance->raw_data[11], + instance->raw_data[12], + (unsigned)scramble, + instance->raw_data[6], + instance->raw_data[7], + (unsigned)fixed, + (unsigned int)instance->generic.serial, + (unsigned)counter, + (unsigned)instance->generic.btn, + fiat_marelli_button_name(instance->generic.btn), + (unsigned)epoch, + variant, + (unsigned long)instance->te_detected); +} diff --git a/protocols/fiat_v1.h b/protocols/fiat_v1.h new file mode 100644 index 0000000..6974f95 --- /dev/null +++ b/protocols/fiat_v1.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../defines.h" + +#define FIAT_MARELLI_PROTOCOL_NAME "Fiat V1" + +typedef struct SubGhzProtocolDecoderFiatMarelli SubGhzProtocolDecoderFiatMarelli; + +extern const SubGhzProtocol fiat_v1_protocol; + +void* subghz_protocol_decoder_fiat_marelli_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_fiat_marelli_free(void* context); +void subghz_protocol_decoder_fiat_marelli_reset(void* context); +void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_fiat_marelli_get_hash_data(void* context); +SubGhzProtocolStatus subghz_protocol_decoder_fiat_marelli_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); +SubGhzProtocolStatus + subghz_protocol_decoder_fiat_marelli_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString* output); diff --git a/protocols/mazda_v0.c b/protocols/mazda_v0.c new file mode 100644 index 0000000..6e41be4 --- /dev/null +++ b/protocols/mazda_v0.c @@ -0,0 +1,350 @@ +#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 + +static const SubGhzBlockConst subghz_protocol_mazda_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; + +struct SubGhzProtocolDecoderMazda { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t preamble_count; + uint16_t bit_counter; + uint8_t prev_state; + uint8_t data_buffer[MAZDA_DATA_BUFFER_SIZE]; +}; + +// ============================================================================ +// 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, +}; + +const SubGhzProtocolEncoder subghz_protocol_mazda_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol mazda_v0_protocol = { + .name = MAZDA_PROTOCOL_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + .decoder = &subghz_protocol_mazda_decoder, + .encoder = &subghz_protocol_mazda_encoder, +}; + +// ============================================================================ +// Decoder +// ============================================================================ + +void* subghz_protocol_decoder_mazda_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMazda* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazda)); + furi_check(instance); + instance->base.protocol = &mazda_v0_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_mazda_free(void* context) { + furi_check(context); + SubGhzProtocolDecoderMazda* instance = context; + free(instance); +} + +void subghz_protocol_decoder_mazda_reset(void* context) { + 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)); +} + +void subghz_protocol_decoder_mazda_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + UNUSED(level); + SubGhzProtocolDecoderMazda* instance = context; + + switch(instance->decoder.parser_step) { + case MazdaDecoderStepReset: + if(mazda_is_short(duration)) { + instance->decoder.te_last = duration; + instance->preamble_count = 0; + instance->decoder.parser_step = MazdaDecoderStepPreambleCheck; + } + 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; + } + break; + + case MazdaDecoderStepDataSave: + instance->decoder.te_last = duration; + instance->decoder.parser_step = MazdaDecoderStepDataCheck; + break; + + 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); + } + instance->decoder.parser_step = MazdaDecoderStepReset; + } + break; + } +} + +uint8_t subghz_protocol_decoder_mazda_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); +} + +SubGhzProtocolStatus subghz_protocol_decoder_mazda_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_check(context); + SubGhzProtocolDecoderMazda* instance = context; + return 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); + } + return ret; +} + +void subghz_protocol_decoder_mazda_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderMazda* instance = context; + mazda_parse_data(&instance->generic); + + uint8_t data[8]; + for(uint8_t i = 0; i < 8; i++) { + data[i] = (instance->generic.data >> (56 - 8 * i)) & 0xFF; + } + + 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", + 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]); +} diff --git a/protocols/mazda_v0.h b/protocols/mazda_v0.h new file mode 100644 index 0000000..f71e508 --- /dev/null +++ b/protocols/mazda_v0.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../defines.h" + +#define MAZDA_PROTOCOL_NAME "Mazda V0" + +typedef struct SubGhzProtocolDecoderMazda SubGhzProtocolDecoderMazda; + +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* 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); diff --git a/protocols/mitsubishi_v0.c b/protocols/mitsubishi_v0.c new file mode 100644 index 0000000..6e79c5e --- /dev/null +++ b/protocols/mitsubishi_v0.c @@ -0,0 +1,261 @@ +#include "mitsubishi_v0.h" +#include + +// Original implementation by @lupettohf + +#define MITSUBISHI_BIT_COUNT 96 +#define MITSUBISHI_DATA_BYTES 12 + +static const SubGhzBlockConst subghz_protocol_mitsubishi_const = { + .te_short = 250, + .te_long = 500, + .te_delta = 100, + .min_count_bit_for_found = 80, +}; + +typedef enum { + MitsubishiDecoderStepReset = 0, + MitsubishiDecoderStepDataSave, + MitsubishiDecoderStepDataCheck, +} MitsubishiDecoderStep; + +struct SubGhzProtocolDecoderMitsubishi { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint8_t decoder_state; + uint16_t bit_count; + uint8_t decode_data[MITSUBISHI_DATA_BYTES]; +}; + +static void mitsubishi_unscramble_payload(uint8_t* payload) { + for(uint8_t i = 0; i < 8; i++) { + payload[i] = (uint8_t)~payload[i]; + } + + uint16_t counter = ((uint16_t)payload[4] << 8) | payload[5]; + uint8_t hi = (counter >> 8) & 0xFF; + uint8_t lo = counter & 0xFF; + uint8_t mask1 = (hi & 0xAAU) | (lo & 0x55U); + uint8_t mask2 = (lo & 0xAAU) | (hi & 0x55U); + uint8_t mask3 = mask1 ^ mask2; + + for(uint8_t i = 0; i < 5; i++) { + payload[i] ^= mask3; + } +} + +static inline bool mitsubishi_is_short(uint32_t duration) { + return DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) < + subghz_protocol_mitsubishi_const.te_delta; +} + +static inline bool mitsubishi_is_long(uint32_t duration) { + return DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) < + subghz_protocol_mitsubishi_const.te_delta; +} + +static void mitsubishi_reset_payload(SubGhzProtocolDecoderMitsubishi* instance) { + instance->bit_count = 0; + memset(instance->decode_data, 0, sizeof(instance->decode_data)); +} + +static bool mitsubishi_collect_pair(SubGhzProtocolDecoderMitsubishi* instance, uint32_t high, uint32_t low) { + bool bit_value; + + if(mitsubishi_is_short(high) && mitsubishi_is_long(low)) { + bit_value = true; + } else if(mitsubishi_is_long(high) && mitsubishi_is_short(low)) { + bit_value = false; + } else { + return false; + } + + uint16_t bit_index = instance->bit_count; + if(bit_index < MITSUBISHI_BIT_COUNT) { + if(bit_value) { + uint8_t byte_index = bit_index >> 3; + uint8_t bit_position = 7 - (bit_index & 0x07); + instance->decode_data[byte_index] |= (1U << bit_position); + } + instance->bit_count++; + } + + return true; +} + +static void mitsubishi_publish_frame(SubGhzProtocolDecoderMitsubishi* instance) { + uint8_t payload[MITSUBISHI_DATA_BYTES]; + memcpy(payload, instance->decode_data, sizeof(payload)); + mitsubishi_unscramble_payload(payload); + + instance->generic.data_count_bit = instance->bit_count; + instance->generic.serial = + ((uint32_t)payload[0] << 24) | ((uint32_t)payload[1] << 16) | ((uint32_t)payload[2] << 8) | + payload[3]; + instance->generic.cnt = ((uint16_t)payload[4] << 8) | payload[5]; + instance->generic.btn = payload[6]; + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } +} + +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 mitsubishi_v0_protocol = { + .name = MITSUBISHI_PROTOCOL_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + .decoder = &subghz_protocol_mitsubishi_decoder, + .encoder = &subghz_protocol_mitsubishi_encoder, +}; + +void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi)); + furi_check(instance); + instance->base.protocol = &mitsubishi_v0_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_mitsubishi_free(void* context) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + free(instance); +} + +void subghz_protocol_decoder_mitsubishi_reset(void* context) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + instance->decoder_state = MitsubishiDecoderStepReset; + instance->decoder.te_last = 0; + instance->generic.data_count_bit = 0; + mitsubishi_reset_payload(instance); +} + +void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + + switch(instance->decoder_state) { + case MitsubishiDecoderStepReset: + if(level) { + instance->decoder.te_last = duration; + instance->decoder_state = MitsubishiDecoderStepDataCheck; + } + break; + + case MitsubishiDecoderStepDataSave: + if(level) { + instance->decoder.te_last = duration; + instance->decoder_state = MitsubishiDecoderStepDataCheck; + } else { + instance->decoder_state = MitsubishiDecoderStepReset; + mitsubishi_reset_payload(instance); + } + break; + + case MitsubishiDecoderStepDataCheck: + if(!level) { + if(mitsubishi_collect_pair(instance, instance->decoder.te_last, duration)) { + if(instance->bit_count >= MITSUBISHI_BIT_COUNT) { + mitsubishi_publish_frame(instance); + mitsubishi_reset_payload(instance); + instance->decoder_state = MitsubishiDecoderStepReset; + } else { + instance->decoder_state = MitsubishiDecoderStepDataSave; + } + } else { + mitsubishi_reset_payload(instance); + instance->decoder_state = MitsubishiDecoderStepReset; + } + } else { + instance->decoder.te_last = duration; + } + break; + } +} + +uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + uint8_t hash = 0; + for(size_t i = 0; i < sizeof(instance->decode_data); i++) { + hash ^= instance->decode_data[i]; + } + return hash; +} + +SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret == SubGhzProtocolStatusOk) { + flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1); + flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1); + uint32_t btn = instance->generic.btn; + flipper_format_write_uint32(flipper_format, "Btn", &btn, 1); + } + return ret; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_mitsubishi_const.min_count_bit_for_found); + + if(ret == SubGhzProtocolStatusOk) { + flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1); + flipper_format_read_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1); + uint32_t btn = 0; + flipper_format_read_uint32(flipper_format, "Btn", &btn, 1); + instance->generic.btn = (uint8_t)btn; + } + + return ret; +} + +void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderMitsubishi* instance = context; + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Sn:%08lX Cnt:%04lX\r\n" + "Btn:%02X\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + instance->generic.serial, + instance->generic.cnt, + instance->generic.btn); +} diff --git a/protocols/mitsubishi_v0.h b/protocols/mitsubishi_v0.h new file mode 100644 index 0000000..b07b0d1 --- /dev/null +++ b/protocols/mitsubishi_v0.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../defines.h" + +#define MITSUBISHI_PROTOCOL_NAME "Mitsubishi V0" + +typedef struct SubGhzProtocolDecoderMitsubishi SubGhzProtocolDecoderMitsubishi; + +extern const SubGhzProtocol mitsubishi_v0_protocol; + +void* subghz_protocol_decoder_mitsubishi_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_mitsubishi_free(void* context); +void subghz_protocol_decoder_mitsubishi_reset(void* context); +void subghz_protocol_decoder_mitsubishi_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_mitsubishi_get_hash_data(void* context); +SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); +SubGhzProtocolStatus + subghz_protocol_decoder_mitsubishi_deserialize(void* context, FlipperFormat* flipper_format); +void subghz_protocol_decoder_mitsubishi_get_string(void* context, FuriString* output); diff --git a/protocols/porsche_touareg.c b/protocols/porsche_touareg.c new file mode 100644 index 0000000..d33f49f --- /dev/null +++ b/protocols/porsche_touareg.c @@ -0,0 +1,372 @@ +#include "porsche_touareg.h" +#include + +// Original implementation by @lupettohf + +#define PORSCHE_CAYENNE_BIT_COUNT 64 +#define PC_TE_SYNC 3370U +#define PC_TE_GAP 5930U +#define PC_SYNC_MIN 15 + +static const SubGhzBlockConst subghz_protocol_porsche_cayenne_const = { + .te_short = 1680, + .te_long = 3370, + .te_delta = 500, + .min_count_bit_for_found = PORSCHE_CAYENNE_BIT_COUNT, +}; + +typedef enum { + PorscheCayenneDecoderStepReset = 0, + PorscheCayenneDecoderStepSync, + PorscheCayenneDecoderStepGapHigh, + PorscheCayenneDecoderStepGapLow, + PorscheCayenneDecoderStepData, +} PorscheCayenneDecoderStep; + +struct SubGhzProtocolDecoderPorscheCayenne { + SubGhzProtocolDecoderBase base; + SubGhzBlockDecoder decoder; + SubGhzBlockGeneric generic; + + uint16_t sync_count; + uint64_t raw_data; + uint8_t bit_count; +}; + +static void porsche_cayenne_compute_frame( + uint32_t serial24, + uint8_t btn, + uint16_t counter, + uint8_t frame_type, + uint8_t* pkt) { + uint8_t b0 = (uint8_t)((btn << 4) | (frame_type & 0x07)); + uint8_t b1 = (serial24 >> 16) & 0xFF; + uint8_t b2 = (serial24 >> 8) & 0xFF; + uint8_t b3 = serial24 & 0xFF; + + uint16_t cnt = counter + 1; + uint8_t cnt_lo = cnt & 0xFF; + uint8_t cnt_hi = (cnt >> 8) & 0xFF; + + uint8_t r_h = b3; + uint8_t r_m = b1; + uint8_t r_l = b2; + +#define ROTATE24(rh, rm, rl) \ + do { \ + uint8_t _ch = ((rh) >> 7) & 1U; \ + uint8_t _cm = ((rm) >> 7) & 1U; \ + uint8_t _cl = ((rl) >> 7) & 1U; \ + (rh) = (uint8_t)(((rh) << 1) | _cm); \ + (rm) = (uint8_t)(((rm) << 1) | _cl); \ + (rl) = (uint8_t)(((rl) << 1) | _ch); \ + } while(0) + + for(uint8_t i = 0; i < 4; i++) { + ROTATE24(r_h, r_m, r_l); + } + for(uint16_t i = 0; i < cnt_lo; i++) { + ROTATE24(r_h, r_m, r_l); + } + +#undef ROTATE24 + + uint8_t a9a = r_h ^ b0; + + uint8_t nb9b_p1 = (uint8_t)((~cnt_lo << 2) & 0xFC) ^ r_m; + uint8_t nb9b_p2 = (uint8_t)((~cnt_hi << 2) & 0xFC) ^ r_m; + uint8_t nb9b_p3 = (uint8_t)((~cnt_hi >> 6) & 0x03) ^ r_m; + uint8_t a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03); + + uint8_t nb9c_p1 = (uint8_t)((~cnt_lo >> 2) & 0x3F) ^ r_l; + uint8_t nb9c_p2 = (uint8_t)((~cnt_hi & 0x03) << 6) ^ r_l; + uint8_t nb9c_p3 = (uint8_t)((~cnt_hi >> 2) & 0x3F) ^ r_l; + uint8_t a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C); + + pkt[0] = b0; + pkt[1] = b1; + pkt[2] = b2; + pkt[3] = b3; + pkt[4] = (uint8_t)(((a9a >> 2) & 0x3F) | ((~cnt_lo & 0x03U) << 6)); + pkt[5] = (uint8_t)( + (~cnt_lo & 0xC0U) | ((a9a & 0x03U) << 4) | (a9b & 0x0CU) | ((~cnt_lo >> 2) & 0x03U)); + pkt[6] = (uint8_t)(((a9b & 0x03U) << 6) | ((a9c >> 2) & 0x3CU) | ((~cnt_lo >> 4) & 0x03U)); + pkt[7] = (uint8_t)(((a9b >> 4) & 0x0FU) | ((a9c & 0x0FU) << 4)); +} + +static void porsche_cayenne_parse_data(SubGhzProtocolDecoderPorscheCayenne* instance) { + uint8_t pkt[8]; + uint64_t raw = instance->generic.data; + + for(int8_t i = 7; i >= 0; i--) { + pkt[i] = (uint8_t)(raw & 0xFF); + raw >>= 8; + } + + instance->generic.serial = ((uint32_t)pkt[1] << 16) | ((uint32_t)pkt[2] << 8) | pkt[3]; + instance->generic.btn = (uint8_t)(pkt[0] >> 4); + instance->generic.cnt = 0; + + uint8_t frame_type = pkt[0] & 0x07; + uint8_t try_pkt[8]; + for(uint16_t try_cnt = 1; try_cnt <= 256; try_cnt++) { + porsche_cayenne_compute_frame( + instance->generic.serial, + instance->generic.btn, + (uint16_t)(try_cnt - 1), + frame_type, + try_pkt); + if(try_pkt[4] == pkt[4] && try_pkt[5] == pkt[5] && try_pkt[6] == pkt[6] && + try_pkt[7] == pkt[7]) { + instance->generic.cnt = try_cnt; + break; + } + } +} + +static void porsche_cayenne_publish_frame(SubGhzProtocolDecoderPorscheCayenne* instance) { + instance->generic.data = instance->raw_data; + instance->generic.data_count_bit = PORSCHE_CAYENNE_BIT_COUNT; + porsche_cayenne_parse_data(instance); + + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } +} + +const SubGhzProtocolDecoder subghz_protocol_porsche_cayenne_decoder = { + .alloc = subghz_protocol_decoder_porsche_cayenne_alloc, + .free = subghz_protocol_decoder_porsche_cayenne_free, + .feed = subghz_protocol_decoder_porsche_cayenne_feed, + .reset = subghz_protocol_decoder_porsche_cayenne_reset, + .get_hash_data = subghz_protocol_decoder_porsche_cayenne_get_hash_data, + .serialize = subghz_protocol_decoder_porsche_cayenne_serialize, + .deserialize = subghz_protocol_decoder_porsche_cayenne_deserialize, + .get_string = subghz_protocol_decoder_porsche_cayenne_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_porsche_cayenne_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol porsche_touareg_protocol = { + .name = PORSCHE_CAYENNE_PROTOCOL_NAME, + .type = SubGhzProtocolTypeDynamic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | + SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + .decoder = &subghz_protocol_porsche_cayenne_decoder, + .encoder = &subghz_protocol_porsche_cayenne_encoder, +}; + +void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + SubGhzProtocolDecoderPorscheCayenne* instance = + calloc(1, sizeof(SubGhzProtocolDecoderPorscheCayenne)); + furi_check(instance); + instance->base.protocol = &porsche_touareg_protocol; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void subghz_protocol_decoder_porsche_cayenne_free(void* context) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + free(instance); +} + +void subghz_protocol_decoder_porsche_cayenne_reset(void* context) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + instance->decoder.te_last = 0; + instance->sync_count = 0; + instance->raw_data = 0; + instance->bit_count = 0; + instance->generic.data = 0; + instance->generic.data_count_bit = 0; +} + +void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + + const uint32_t te_short = subghz_protocol_porsche_cayenne_const.te_short; + const uint32_t te_long = subghz_protocol_porsche_cayenne_const.te_long; + const uint32_t te_delta = subghz_protocol_porsche_cayenne_const.te_delta; + + switch(instance->decoder.parser_step) { + case PorscheCayenneDecoderStepReset: + if(!level && DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { + instance->sync_count = 1; + instance->decoder.parser_step = PorscheCayenneDecoderStepSync; + } + break; + + case PorscheCayenneDecoderStepSync: + if(level) { + if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { + // keep collecting sync pairs + } else if( + instance->sync_count >= PC_SYNC_MIN && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { + instance->decoder.parser_step = PorscheCayenneDecoderStepGapLow; + } else { + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) { + instance->sync_count++; + } else if( + instance->sync_count >= PC_SYNC_MIN && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { + instance->decoder.parser_step = PorscheCayenneDecoderStepGapHigh; + } else { + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + } + } + break; + + case PorscheCayenneDecoderStepGapHigh: + if(level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { + instance->raw_data = 0; + instance->bit_count = 0; + instance->decoder.parser_step = PorscheCayenneDecoderStepData; + } else { + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + } + break; + + case PorscheCayenneDecoderStepGapLow: + if(!level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) { + instance->raw_data = 0; + instance->bit_count = 0; + instance->decoder.parser_step = PorscheCayenneDecoderStepData; + } else { + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + } + break; + + case PorscheCayenneDecoderStepData: + if(level) { + bool bit_value = false; + if(DURATION_DIFF(instance->decoder.te_last, te_short) < te_delta && + DURATION_DIFF(duration, te_long) < te_delta) { + bit_value = false; + } else if( + DURATION_DIFF(instance->decoder.te_last, te_long) < te_delta && + DURATION_DIFF(duration, te_short) < te_delta) { + bit_value = true; + } else { + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + break; + } + + instance->raw_data = (instance->raw_data << 1) | (bit_value ? 1U : 0U); + instance->bit_count++; + + if(instance->bit_count >= PORSCHE_CAYENNE_BIT_COUNT) { + porsche_cayenne_publish_frame(instance); + instance->decoder.parser_step = PorscheCayenneDecoderStepReset; + } + } else { + instance->decoder.te_last = duration; + } + break; + } +} + +uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* 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); +} + +SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + + SubGhzProtocolStatus ret = + subghz_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret == SubGhzProtocolStatusOk) { + uint32_t serial = instance->generic.serial & 0xFFFFFF; + flipper_format_write_uint32(flipper_format, "Serial", &serial, 1); + uint32_t cnt = instance->generic.cnt; + flipper_format_write_uint32(flipper_format, "Cnt", &cnt, 1); + uint32_t btn = instance->generic.btn; + flipper_format_write_uint32(flipper_format, "Btn", &btn, 1); + } + + return ret; +} + +SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + + SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + subghz_protocol_porsche_cayenne_const.min_count_bit_for_found); + + if(ret == SubGhzProtocolStatusOk) { + porsche_cayenne_parse_data(instance); + + uint32_t serial = 0; + if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) { + instance->generic.serial = serial & 0xFFFFFF; + } + + uint32_t cnt = 0; + if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) { + instance->generic.cnt = cnt; + } + + uint32_t btn = 0; + if(flipper_format_read_uint32(flipper_format, "Btn", &btn, 1)) { + instance->generic.btn = (uint8_t)btn; + } + } + + return ret; +} + +void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output) { + furi_check(context); + SubGhzProtocolDecoderPorscheCayenne* instance = context; + + uint8_t frame_type = (uint8_t)((instance->generic.data >> 56) & 0x07); + const char* frame_type_name = "??"; + if(frame_type == 0x02) { + frame_type_name = "First"; + } else if(frame_type == 0x01) { + frame_type_name = "Cont"; + } else if(frame_type == 0x04) { + frame_type_name = "Final"; + } + + furi_string_cat_printf( + output, + "%s %dbit\r\n" + "Sn:%06lX Btn:%X\r\n" + "Cnt:%04lX FT:%s\r\n" + "Raw:%08lX%08lX\r\n", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (unsigned long)(instance->generic.serial & 0xFFFFFF), + (unsigned int)instance->generic.btn, + (unsigned long)instance->generic.cnt, + frame_type_name, + (unsigned long)(instance->generic.data >> 32), + (unsigned long)(instance->generic.data & 0xFFFFFFFF)); +} diff --git a/protocols/porsche_touareg.h b/protocols/porsche_touareg.h new file mode 100644 index 0000000..156d677 --- /dev/null +++ b/protocols/porsche_touareg.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../defines.h" + +#define PORSCHE_CAYENNE_PROTOCOL_NAME "Porsche Touareg" + +typedef struct SubGhzProtocolDecoderPorscheCayenne SubGhzProtocolDecoderPorscheCayenne; + +extern const SubGhzProtocol porsche_touareg_protocol; + +void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment); +void subghz_protocol_decoder_porsche_cayenne_free(void* context); +void subghz_protocol_decoder_porsche_cayenne_reset(void* context); +void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration); +uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context); +SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); +SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize( + void* context, + FlipperFormat* flipper_format); +void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output); diff --git a/protocols/protocol_items.c b/protocols/protocol_items.c index f76a26b..4bbf59d 100644 --- a/protocols/protocol_items.c +++ b/protocols/protocol_items.c @@ -11,6 +11,10 @@ const SubGhzProtocol* protopirate_protocol_registry_items[] = { &kia_protocol_v6, // Heap: free 18296 &ford_protocol_v0, // Heap: free 19456 &fiat_protocol_v0, // Heap: free 16864 + &fiat_v1_protocol, + &mazda_v0_protocol, + &mitsubishi_v0_protocol, + &porsche_touareg_protocol, &subaru_protocol, // Heap: free 17280 &suzuki_protocol, // Heap: free 16064 &vag_protocol, // Heap: free 29352 @@ -97,6 +101,38 @@ static const ProtoPirateProtocolTiming protocol_timings[] = { .te_delta = 100, .min_count_bit = 64, }, + // Fiat V1: Manchester dynamic (baseline Type A 260/520us) + { + .name = "Fiat V1", + .te_short = 260, + .te_long = 520, + .te_delta = 80, + .min_count_bit = 80, + }, + // Mazda V0: 250/500us + { + .name = "Mazda V0", + .te_short = 250, + .te_long = 500, + .te_delta = 100, + .min_count_bit = 64, + }, + // Mitsubishi V0: 250/500us + { + .name = "Mitsubishi V0", + .te_short = 250, + .te_long = 500, + .te_delta = 100, + .min_count_bit = 80, + }, + // Porsche Touareg: 1680/3370us + { + .name = "Porsche Touareg", + .te_short = 1680, + .te_long = 3370, + .te_delta = 500, + .min_count_bit = 64, + }, // Subaru: PPM 800/1600µs { .name = "Subaru", diff --git a/protocols/protocol_items.h b/protocols/protocol_items.h index dd01614..00c3c90 100644 --- a/protocols/protocol_items.h +++ b/protocols/protocol_items.h @@ -13,6 +13,10 @@ #include "kia_v6.h" #include "ford_v0.h" #include "fiat_v0.h" +#include "fiat_v1.h" +#include "mazda_v0.h" +#include "mitsubishi_v0.h" +#include "porsche_touareg.h" #include "subaru.h" #include "suzuki.h" #include "vag.h"