Compare commits

...

6 Commits

Author SHA1 Message Date
d4rks1d33 6ca56247d7 more test updates, don't flash 2026-04-28 16:16:32 +00:00
d4rks1d33 abdb41a4ea Not on my main pc, pushing things for later, very unestable things, don't flash 2026-04-28 15:32:40 +00:00
d4rks1d33 3b216a3eac testing things 2026-04-27 17:50:53 +00:00
d4rks1d33 9e09b49469 Update 2026-03-25 22:03:15 -03:00
d4rks1d33 ad4cd89dc4 Update 2026-03-25 18:33:47 +00:00
d4rks1d33 692d223570 Added placeholders for Renault protocols (Valeo, Siemens, Hitag) 2026-03-25 18:24:14 +00:00
41 changed files with 16938 additions and 2486 deletions
+722
View File
@@ -0,0 +1,722 @@
#include "fiat_v0.h"
#include <lib/toolbox/manchester_decoder.h>
#include <inttypes.h>
#define TAG "FiatProtocolV0"
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
#define FIAT_V0_PREAMBLE_PAIRS 150
#define FIAT_V0_GAP_US 800
#define FIAT_V0_TOTAL_BURSTS 3
#define FIAT_V0_INTER_BURST_GAP 25000
static const SubGhzBlockConst subghz_protocol_fiat_v0_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
struct SubGhzProtocolDecoderFiatV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint8_t decoder_state;
uint16_t preamble_count;
uint32_t data_low;
uint32_t data_high;
uint8_t bit_count;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
uint8_t final_count;
uint32_t te_last;
};
struct SubGhzProtocolEncoderFiatV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t hop;
uint32_t fix;
uint8_t endbyte;
// Capacity of encoder.upload in LevelDuration elements.
size_t upload_capacity;
};
typedef enum {
FiatV0DecoderStepReset = 0,
FiatV0DecoderStepPreamble = 1,
FiatV0DecoderStepData = 2,
} FiatV0DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
.free = subghz_protocol_decoder_fiat_v0_free,
.feed = subghz_protocol_decoder_fiat_v0_feed,
.reset = subghz_protocol_decoder_fiat_v0_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v0_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v0_serialize,
.deserialize = subghz_protocol_decoder_fiat_v0_deserialize,
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
.free = subghz_protocol_encoder_fiat_v0_free,
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
.stop = subghz_protocol_encoder_fiat_v0_stop,
.yield = subghz_protocol_encoder_fiat_v0_yield,
};
const SubGhzProtocol fiat_protocol_v0 = {
.name = FIAT_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_fiat_v0_decoder,
.encoder = &subghz_protocol_fiat_v0_encoder,
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
static size_t fiat_v0_encoder_calc_required_upload(void) {
// Per burst:
// - preamble: FIAT_V0_PREAMBLE_PAIRS pairs -> 2 elements each
// - data: 64 bits Manchester -> 2 elements per bit
// - endbyte: 7 bits Manchester -> 2 elements per bit
// - trailer: 1 element (extended low)
const size_t per_burst = (FIAT_V0_PREAMBLE_PAIRS * 2) + (64 * 2) + (7 * 2) + 1;
// Inter-burst gap is a single element between bursts.
return (FIAT_V0_TOTAL_BURSTS * per_burst) +
(FIAT_V0_TOTAL_BURSTS > 0 ? (FIAT_V0_TOTAL_BURSTS - 1) : 0);
}
static void
fiat_v0_encoder_ensure_upload_capacity(SubGhzProtocolEncoderFiatV0* instance, size_t required) {
furi_check(instance);
furi_check(required <= instance->upload_capacity);
LevelDuration* new_upload =
realloc(instance->encoder.upload, required * sizeof(LevelDuration));
furi_check(new_upload);
instance->encoder.upload = new_upload;
instance->upload_capacity = required;
}
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0));
furi_check(instance);
instance->base.protocol = &fiat_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->upload_capacity = fiat_v0_encoder_calc_required_upload();
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
furi_check(instance);
size_t index = 0;
const size_t required = fiat_v0_encoder_calc_required_upload();
fiat_v0_encoder_ensure_upload_capacity(instance, required);
uint32_t te_short = subghz_protocol_fiat_v0_const.te_short;
FURI_LOG_I(
TAG,
"Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, FIAT_V0_INTER_BURST_GAP);
}
// Preamble: alternating short pulses
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
// Extend last LOW to create the gap (~FIAT_V0_GAP_US)
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US);
// Combine hop and fix into 64-bit data
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
// Manchester encode 64 bits of data
for(int bit = 63; bit >= 0; bit--) {
bool curr_bit = (data >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits
uint8_t endbyte = (uint8_t)(instance->endbyte & 0x7F);
for(int bit = 6; bit >= 0; bit--) {
bool curr_bit = (endbyte >> bit) & 1;
if(curr_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
}
}
// End with extended LOW (will be followed by inter-burst gap or end)
instance->encoder.upload[index++] = level_duration_make(false, te_short * 4);
}
furi_check(index <= instance->upload_capacity);
instance->encoder.size_upload = index;
instance->encoder.front = 0;
FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload);
}
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 10;
flipper_format_rewind(flipper_format);
FuriString* temp_str = furi_string_alloc();
furi_check(temp_str);
do {
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str));
break;
}
uint32_t bit_count_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
// This protocol transmits 71 bits: 64-bit key + 7-bit endbyte.
// Let's do according to what's read, (64 or 71), else 71
if(bit_count_temp == 64 || bit_count_temp == 71) {
instance->generic.data_count_bit = bit_count_temp;
} else {
FURI_LOG_E(
TAG,
"Inconsistent Bit value of %d was defaulted to 71",
instance->generic.data_count_bit);
instance->generic.data_count_bit = 71;
}
} else {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = (uint8_t)(c - '0');
} else if(c >= 'A' && c <= 'F') {
nibble = (uint8_t)(c - 'A' + 10);
} else if(c >= 'a' && c <= 'f') {
nibble = (uint8_t)(c - 'a' + 10);
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Key parse error: expected 16 hex nibbles, got %u", (unsigned)hex_pos);
break;
}
instance->generic.data = key;
instance->hop = (uint32_t)(key >> 32);
instance->fix = (uint32_t)(key & 0xFFFFFFFF);
uint32_t btn_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->endbyte = (uint8_t)(btn_temp & 0x7F);
} else {
instance->endbyte = 0;
}
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
instance->generic.serial = instance->fix;
uint32_t repeat_temp = 0;
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_temp, 1)) {
instance->encoder.repeat = repeat_temp;
} else {
instance->encoder.repeat = 10;
}
subghz_protocol_encoder_fiat_v0_get_upload(instance);
instance->encoder.is_running = true;
FURI_LOG_I(
TAG,
"Encoder ready: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
instance->hop,
instance->fix,
instance->endbyte);
ret = SubGhzProtocolStatusOk;
} while(false);
furi_string_free(temp_str);
return ret;
}
void subghz_protocol_encoder_fiat_v0_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderFiatV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0));
furi_check(instance);
instance->base.protocol = &fiat_protocol_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_v0_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_v0_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
instance->decoder.parser_step = FiatV0DecoderStepReset;
instance->decoder_state = 0;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->hop = 0;
instance->fix = 0;
instance->endbyte = 0;
instance->final_count = 0;
instance->te_last = 0;
instance->manchester_state = ManchesterStateMid1;
}
// Helper function to reset decoder to data state with proper Manchester initialization
static void
fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) {
instance->decoder_state = FiatV0DecoderStepData;
instance->preamble_count = 0;
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->te_last = duration;
// Critical: Reset Manchester state when entering data mode
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short;
uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long;
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta;
uint32_t gap_threshold = FIAT_V0_GAP_US;
uint32_t diff;
switch(instance->decoder_state) {
case FiatV0DecoderStepReset:
if(!level) {
return;
}
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->data_low = 0;
instance->data_high = 0;
instance->decoder_state = FiatV0DecoderStepPreamble;
instance->te_last = duration;
instance->preamble_count = 0;
instance->bit_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case FiatV0DecoderStepPreamble:
if(level) {
// HIGH pulse during preamble - just track timing
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
instance->preamble_count++;
instance->te_last = duration;
} else {
instance->decoder_state = FiatV0DecoderStepReset;
}
return;
}
// LOW pulse - check if it's the gap after preamble
if(duration < te_short) {
diff = te_short - duration;
} else {
diff = duration - te_short;
}
if(diff < te_delta) {
// Normal short LOW - continue preamble
instance->preamble_count++;
instance->te_last = duration;
} else {
// Not a short pulse - check if it's the gap
if(instance->preamble_count >= 0x96) {
// We have enough preamble, check for gap
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
// Valid gap detected - transition to data state
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
// Invalid timing or not enough preamble
instance->decoder_state = FiatV0DecoderStepReset;
}
// Also check for gap even with valid short timing if we have enough preamble
if(instance->preamble_count >= 0x96 &&
instance->decoder_state == FiatV0DecoderStepPreamble) {
if(duration < gap_threshold) {
diff = gap_threshold - duration;
} else {
diff = duration - gap_threshold;
}
if(diff < te_delta) {
// Valid gap detected - transition to data state
fiat_v0_decoder_enter_data_state(instance, duration);
return;
}
}
break;
case FiatV0DecoderStepData:
ManchesterEvent event = ManchesterEventReset;
if(duration < te_short) {
diff = te_short - duration;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
}
} else {
diff = duration - te_short;
if(diff < te_delta) {
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
} else {
if(duration < te_long) {
diff = te_long - duration;
} else {
diff = duration - te_long;
}
if(diff < te_delta) {
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
}
}
}
if(event != ManchesterEventReset) {
bool data_bit_bool;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit_bool)) {
uint32_t new_bit = data_bit_bool ? 1 : 0;
uint32_t carry = (instance->data_low >> 31) & 1;
instance->data_low = (instance->data_low << 1) | new_bit;
instance->data_high = (instance->data_high << 1) | carry;
instance->bit_count++;
if(instance->bit_count == 0x40) {
instance->fix = instance->data_low;
instance->hop = instance->data_high;
FURI_LOG_D(
TAG,
"Bit 64: fix=0x%08lX, hop=0x%08lX, clearing data_low/data_high",
instance->fix,
instance->hop);
instance->data_low = 0;
instance->data_high = 0;
}
#ifndef REMOVE_LOGS
if(instance->bit_count > 0x40 && instance->bit_count <= 0x47) {
uint8_t endbyte_bit_num = instance->bit_count - 0x40;
uint8_t endbyte_bit_index = endbyte_bit_num - 1;
uint8_t data_low_byte = (uint8_t)instance->data_low;
char binary_str[9] = {0};
for(int i = 7; i >= 0; i--) {
binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0';
}
FURI_LOG_D(
TAG,
"Bit %d (endbyte bit %d/%d): new_bit=%lu, data_low=0x%08lX (0x%02X), binary=0b%s",
instance->bit_count,
endbyte_bit_index,
endbyte_bit_num - 1,
(unsigned long)new_bit,
instance->data_low,
data_low_byte,
binary_str);
}
#endif
if(instance->bit_count == 0x47) {
#ifndef REMOVE_LOGS
uint8_t data_low_byte = (uint8_t)instance->data_low;
char binary_str[9] = {0};
for(int i = 7; i >= 0; i--) {
binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0';
}
FURI_LOG_D(
TAG,
"EXTRACTING AT BIT 71: bit_count=%d, data_low=0x%08lX (0x%02X), binary=0b%s, accumulated %d bits after bit 64",
instance->bit_count,
instance->data_low,
data_low_byte,
binary_str,
instance->bit_count - 0x40);
#endif
instance->final_count = instance->bit_count;
instance->endbyte = (uint8_t)instance->data_low;
FURI_LOG_D(
TAG,
"EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)",
instance->endbyte,
instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
}
}
} else {
if(instance->bit_count == 0x47) {
// We have exactly 71 bits - extract endbyte
uint8_t data_low_byte = (uint8_t)instance->data_low;
instance->endbyte = data_low_byte;
FURI_LOG_D(
TAG,
"GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X",
instance->bit_count,
instance->endbyte & 0x7F);
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
instance->generic.data_count_bit = 71;
instance->generic.serial = instance->fix;
instance->generic.btn = instance->endbyte;
instance->generic.cnt = instance->hop;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_low = 0;
instance->data_high = 0;
instance->bit_count = 0;
instance->decoder_state = FiatV0DecoderStepReset;
} else if(instance->bit_count < 0x40) {
instance->decoder_state = FiatV0DecoderStepReset;
}
}
instance->te_last = duration;
break;
}
}
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* 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_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) break;
if(!flipper_format_write_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(preset->name)))
break;
if(!flipper_format_write_string_cstr(
flipper_format, "Protocol", instance->generic.protocol_name))
break;
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) break;
char key_str[20];
snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix);
if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) break;
uint32_t temp = instance->hop;
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) break;
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->fix, 1)) break;
temp = instance->endbyte;
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) break;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
return subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV0* instance = context;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Hop:%08lX\r\n"
"Sn:%08lX\r\n"
"EndByte:%02X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->hop,
instance->fix,
instance->hop,
instance->fix,
instance->endbyte & 0x7F);
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
extern const SubGhzProtocol fiat_protocol_v0;
// Decoder functions
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v0_free(void* context);
void subghz_protocol_decoder_fiat_v0_reset(void* context);
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output);
// Encoder functions
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_fiat_v0_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_fiat_v0_stop(void* context);
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context);
+544
View File
@@ -0,0 +1,544 @@
#include "fiat_v1.h"
#include <string.h>
#define TAG "FiatProtocolV1"
// 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 (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 35
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 450
#define FIAT_MARELLI_PREAMBLE_MIN 48
#define FIAT_MARELLI_MAX_DATA_BITS 104
#define FIAT_MARELLI_MIN_DATA_BITS 104
#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 uint8_t fiat_marelli_crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x03;
for(size_t i = 0; i < len; i++) {
crc ^= data[i];
for(uint8_t b = 0; b < 8; b++) {
crc = (crc & 0x80) ? ((crc << 1) ^ 0x01) : (crc << 1);
}
}
return crc;
}
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,
} FiatMarelliDecoderStep;
struct SubGhzProtocolDecoderFiatV1 {
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_set_state(
SubGhzProtocolDecoderFiatV1* instance,
FiatMarelliDecoderStep new_state,
const char* reason) {
UNUSED(reason);
instance->decoder_state = new_state;
}
static bool fiat_marelli_get_raw_bit(const uint8_t* raw, uint8_t bit_index) {
return (raw[bit_index / 8] >> (7 - (bit_index % 8))) & 1U;
}
static void fiat_marelli_set_raw_bit(uint8_t* raw, uint8_t bit_index, bool value) {
uint8_t byte_idx = bit_index / 8;
uint8_t mask = 1U << (7 - (bit_index % 8));
if(value) {
raw[byte_idx] |= mask;
} else {
raw[byte_idx] &= (uint8_t)(~mask);
}
}
static void fiat_marelli_rebuild_data_words_from_raw(SubGhzProtocolDecoderFiatV1* instance) {
instance->generic.data = 0;
instance->extra_data = 0;
for(uint8_t i = 0; i < 64; i++) {
instance->generic.data = (instance->generic.data << 1) |
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
}
for(uint8_t i = 64; i < FIAT_MARELLI_MAX_DATA_BITS; i++) {
instance->extra_data = (instance->extra_data << 1) |
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
}
instance->bit_count = FIAT_MARELLI_MAX_DATA_BITS;
instance->generic.data_count_bit = FIAT_MARELLI_MAX_DATA_BITS;
}
static bool fiat_marelli_try_recover_tail_bits(SubGhzProtocolDecoderFiatV1* instance) {
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
return true;
}
if(instance->bit_count < (FIAT_MARELLI_MAX_DATA_BITS - 2)) {
return false;
}
uint8_t missing_bits = FIAT_MARELLI_MAX_DATA_BITS - instance->bit_count;
uint8_t variants = 1U << missing_bits;
uint8_t match_count = 0;
uint8_t matched_variant = 0;
for(uint8_t variant = 0; variant < variants; variant++) {
uint8_t trial[13];
memcpy(trial, instance->raw_data, sizeof(trial));
for(uint8_t i = 0; i < missing_bits; i++) {
bool bit = ((variant >> (missing_bits - 1 - i)) & 1U) != 0;
fiat_marelli_set_raw_bit(trial, instance->bit_count + i, bit);
}
uint8_t calc = fiat_marelli_crc8(trial, 12);
if(calc == trial[12]) {
match_count++;
matched_variant = variant;
}
}
if(match_count != 1) {
return false;
}
for(uint8_t i = 0; i < missing_bits; i++) {
bool bit = ((matched_variant >> (missing_bits - 1 - i)) & 1U) != 0;
fiat_marelli_set_raw_bit(instance->raw_data, instance->bit_count + i, bit);
}
fiat_marelli_rebuild_data_words_from_raw(instance);
return true;
}
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatV1* 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);
fiat_marelli_set_state(instance, FiatMarelliDecoderStepData, "sync accepted");
}
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatV1* 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 0x8:
case 0x7:
return "Lock";
case 0x0:
case 0xB:
return "Unlock";
case 0xD:
return "Trunk";
default:
return "Unknown";
}
}
const SubGhzProtocolDecoder subghz_protocol_fiat_v1_decoder = {
.alloc = subghz_protocol_decoder_fiat_v1_alloc,
.free = subghz_protocol_decoder_fiat_v1_free,
.feed = subghz_protocol_decoder_fiat_v1_feed,
.reset = subghz_protocol_decoder_fiat_v1_reset,
.get_hash_data = subghz_protocol_decoder_fiat_v1_get_hash_data,
.serialize = subghz_protocol_decoder_fiat_v1_serialize,
.deserialize = subghz_protocol_decoder_fiat_v1_deserialize,
.get_string = subghz_protocol_decoder_fiat_v1_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_fiat_v1_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol subghz_protocol_fiat_v1 = {
.name = FIAT_V1_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.decoder = &subghz_protocol_fiat_v1_decoder,
.encoder = &subghz_protocol_fiat_v1_encoder,
};
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderFiatV1* instance =
calloc(1, sizeof(SubGhzProtocolDecoderFiatV1));
furi_check(instance);
instance->base.protocol = &subghz_protocol_fiat_v1;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_fiat_v1_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* instance = context;
free(instance);
}
void subghz_protocol_decoder_fiat_v1_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* instance = context;
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "decoder reset");
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_v1_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* 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) {
fiat_marelli_set_state(instance, FiatMarelliDecoderStepPreamble, "preamble start");
instance->preamble_count = 1;
instance->te_sum = duration;
instance->te_count = 1;
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) {
fiat_marelli_set_state(instance, FiatMarelliDecoderStepSync, "gap detected");
instance->te_last = duration;
} else {
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "gap too short");
}
} else {
fiat_marelli_set_state(
instance, FiatMarelliDecoderStepReset, "preamble too short");
}
} else {
fiat_marelli_set_state(
instance, FiatMarelliDecoderStepReset, "invalid preamble pulse");
}
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 {
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "sync timing mismatch");
}
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_MAX_DATA_BITS - 2)) {
frame_complete = true;
} else {
fiat_marelli_set_state(
instance, FiatMarelliDecoderStepReset, "invalid manchester timing");
}
if(frame_complete) {
instance->generic.data_count_bit = instance->bit_count;
if(!fiat_marelli_try_recover_tail_bits(instance)) {
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
instance->te_last = duration;
break;
}
bool crc_ok = true;
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
crc_ok = (calc == instance->raw_data[12]);
}
if(crc_ok) {
FURI_LOG_D(TAG, "Frame accepted (%u bits, CRC OK)", 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);
}
}
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
}
instance->te_last = duration;
break;
}
}
}
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* 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_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* 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_v1_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* 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) {
if(instance->generic.data_count_bit != FIAT_MARELLI_MAX_DATA_BITS) {
return SubGhzProtocolStatusErrorValueBitCount;
}
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_v1_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderFiatV1* instance = context;
uint8_t epoch = instance->raw_data[6] & 0x0F;
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x03;
uint8_t fixed = instance->raw_data[7] & 0x01;
const char* crc_state = "N/A";
uint8_t crc_value = instance->raw_data[12];
if(instance->bit_count >= 104) {
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
crc_state = (calc == instance->raw_data[12]) ? "OK" : "FAIL";
}
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"
"CRC:%02X [%s]\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,
(unsigned)crc_value,
crc_state);
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define FIAT_V1_PROTOCOL_NAME "Fiat V1"
typedef struct SubGhzProtocolDecoderFiatV1 SubGhzProtocolDecoderFiatV1;
extern const SubGhzProtocol subghz_protocol_fiat_v1;
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_fiat_v1_free(void* context);
void subghz_protocol_decoder_fiat_v1_reset(void* context);
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output);
File diff suppressed because it is too large Load Diff
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define FORD_PROTOCOL_V1_NAME "Ford V1"
typedef struct SubGhzProtocolDecoderFordV1 SubGhzProtocolDecoderFordV1;
extern const SubGhzProtocol ford_protocol_v1;
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_ford_v1_free(void* context);
void subghz_protocol_decoder_ford_v1_reset(void* context);
void subghz_protocol_decoder_ford_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_ford_v1_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_ford_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_ford_v1_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_ford_v1_stop(void* context);
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context);
extern const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder;
+811
View File
@@ -0,0 +1,811 @@
#include "honda_static.h"
#define HONDA_STATIC_BIT_COUNT 64
#define HONDA_STATIC_MIN_SYMBOLS 36
#define HONDA_STATIC_SHORT_BASE_US 28
#define HONDA_STATIC_SHORT_SPAN_US 70
#define HONDA_STATIC_LONG_BASE_US 61
#define HONDA_STATIC_LONG_SPAN_US 130
#define HONDA_STATIC_SYNC_TIME_US 700
#define HONDA_STATIC_ELEMENT_TIME_US 63
#define HONDA_STATIC_UPLOAD_CAPACITY \
(1U + HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT + (2U * HONDA_STATIC_BIT_COUNT) + 1U)
#define HONDA_STATIC_SYMBOL_CAPACITY 512
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
#define HONDA_STATIC_SYMBOL_BYTE_COUNT ((HONDA_STATIC_SYMBOL_CAPACITY + 7U) / 8U)
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
static const char* const honda_static_button_names[9] = {
"Lock",
"Unlock",
"Unknown",
"Trunk",
"Remote Start",
"Unknown",
"Unknown",
"Panic",
"Lock x2",
};
typedef struct {
uint8_t button;
uint8_t _reserved_01[3];
uint32_t serial;
uint32_t counter;
uint8_t checksum;
uint8_t _reserved_0d[3];
} HondaStaticFields;
struct SubGhzProtocolDecoderHondaStatic {
SubGhzProtocolDecoderBase base;
SubGhzBlockGeneric generic;
uint8_t symbols[HONDA_STATIC_SYMBOL_BYTE_COUNT];
uint16_t symbols_count;
};
struct SubGhzProtocolEncoderHondaStatic {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
HondaStaticFields decoded;
uint8_t tx_button;
uint8_t _reserved_69[3];
};
static void honda_static_decoder_commit(
SubGhzProtocolDecoderHondaStatic* instance,
const HondaStaticFields* decoded);
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t value = 0;
for(size_t i = 0; i < 8; i++) {
value = (value << 8U) | bytes[i];
}
return value;
}
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
for(size_t i = 0; i < 8; i++) {
bytes[7U - i] = (uint8_t)(value & 0xFFU);
value >>= 8U;
}
}
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return (uint8_t)value;
}
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
uint32_t value = 0;
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte = data[bit_index >> 3U];
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
value = (value << 1U) | ((byte >> shift) & 1U);
}
return value;
}
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
for(uint8_t i = 0; i < count; i++) {
const uint8_t bit_index = start + i;
const uint8_t byte_index = bit_index >> 3U;
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
const uint8_t mask = (uint8_t)(1U << shift);
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
if(bit) {
data[byte_index] |= mask;
} else {
data[byte_index] &= (uint8_t)~mask;
}
}
}
static uint8_t honda_static_level_u8(bool level) {
return level ? 1U : 0U;
}
static void honda_static_symbol_set(uint8_t* buf, uint16_t index, uint8_t v) {
const uint8_t byte_index = (uint8_t)(index >> 3U);
const uint8_t shift = (uint8_t)(~index) & 0x07U;
const uint8_t mask = (uint8_t)(1U << shift);
if(v) {
buf[byte_index] |= mask;
} else {
buf[byte_index] &= (uint8_t)~mask;
}
}
static uint8_t honda_static_symbol_get(const uint8_t* buf, uint16_t index) {
const uint8_t byte_index = (uint8_t)(index >> 3U);
const uint8_t shift = (uint8_t)(~index) & 0x07U;
return (uint8_t)((buf[byte_index] >> shift) & 1U);
}
static uint8_t honda_static_reverse_bits8(uint8_t value) {
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
return value;
}
static bool honda_static_is_valid_button(uint8_t button) {
if(button > 9U) {
return false;
}
return ((0x336U >> button) & 1U) != 0U;
}
static bool honda_static_is_valid_serial(uint32_t serial) {
return (serial != 0U) && (serial != 0x0FFFFFFFU);
}
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
if(button < 2U) {
return 1U;
}
button -= 2U;
if(button <= 3U) {
return honda_static_encoder_button_map[button];
}
return 1U;
}
static const char* honda_static_button_name(uint8_t button) {
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
return honda_static_button_names[button - 1U];
}
return "Unknown";
}
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
const uint8_t canonical[7] = {
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
compact[5],
compact[6],
compact[7],
};
uint8_t checksum = 0U;
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
checksum ^= canonical[i];
}
return checksum;
}
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
uint8_t compact[8];
honda_static_u64_to_bytes_be(key, compact);
memset(fields, 0, sizeof(*fields));
fields->button = compact[0] & 0x0FU;
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
(uint32_t)compact[7];
fields->checksum = honda_static_compact_bytes_checksum(compact);
}
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
uint8_t compact[8];
compact[0] = fields->button & 0x0FU;
compact[1] = (uint8_t)(fields->serial >> 20U);
compact[2] = (uint8_t)(fields->serial >> 12U);
compact[3] = (uint8_t)(fields->serial >> 4U);
compact[4] = (uint8_t)(fields->serial << 4U);
compact[5] = (uint8_t)(fields->counter >> 16U);
compact[6] = (uint8_t)(fields->counter >> 8U);
compact[7] = (uint8_t)fields->counter;
return honda_static_bytes_to_u64_be(compact);
}
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
memset(packet, 0, 8);
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
honda_static_set_bits(packet, 4, 28, fields->serial);
honda_static_set_bits(packet, 32, 24, fields->counter);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= packet[i];
}
honda_static_set_bits(packet, 56, 8, checksum);
}
static bool
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
const uint8_t button = honda_static_get_bits(packet, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
uint8_t checksum_calc = 0U;
for(size_t i = 0; i < 7; i++) {
checksum_calc ^= packet[i];
}
if(checksum != checksum_calc) {
return false;
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
uint8_t reversed[9];
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
reversed[i] = honda_static_reverse_bits8(packet[i]);
}
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
uint8_t checksum = 0U;
for(size_t i = 0; i < 7; i++) {
checksum ^= reversed[i];
}
if(!honda_static_is_valid_button(button)) {
return false;
}
if(!honda_static_is_valid_serial(serial)) {
return false;
}
fields->button = button;
fields->serial = serial;
fields->counter = counter;
fields->checksum = checksum;
return true;
}
static bool honda_static_manchester_pack_64(
const uint8_t* symbol_bits,
uint16_t count,
uint16_t start_pos,
bool inverted,
uint8_t packet[9],
uint16_t* out_bit_count) {
memset(packet, 0, 9);
uint16_t pos = start_pos;
uint16_t bit_count = 0U;
while((uint16_t)(pos + 1U) < count) {
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
break;
}
const uint8_t a = honda_static_symbol_get(symbol_bits, pos);
const uint8_t b = honda_static_symbol_get(symbol_bits, pos + 1U);
if(a == b) {
pos++;
continue;
}
bool bit = false;
if(inverted) {
bit = (a == 0U) && (b == 1U);
} else {
bit = (a == 1U) && (b == 0U);
}
if(bit) {
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
}
bit_count++;
pos += 2U;
}
if(out_bit_count) {
*out_bit_count = bit_count;
}
return bit_count >= HONDA_STATIC_BIT_COUNT;
}
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
const uint16_t count = instance->symbols_count;
const uint8_t* symbol_bits = instance->symbols;
HondaStaticFields decoded;
uint16_t index = 1U;
uint16_t transitions = 0U;
while(index < count) {
if(honda_static_symbol_get(symbol_bits, index) !=
honda_static_symbol_get(symbol_bits, index - 1U)) {
transitions++;
} else {
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
break;
}
transitions = 0U;
}
index++;
}
if(index >= count) {
return false;
}
while(((uint16_t)(index + 1U) < count) && (honda_static_symbol_get(symbol_bits, index) ==
honda_static_symbol_get(symbol_bits, index + 1U))) {
index++;
}
const uint16_t data_start = index;
uint8_t packet[9] = {0};
uint16_t bit_count = 0U;
if(!honda_static_manchester_pack_64(
symbol_bits, count, data_start, inverted, packet, &bit_count)) {
return false;
}
if(honda_static_validate_forward_packet(packet, &decoded)) {
honda_static_decoder_commit(instance, &decoded);
return true;
}
if(inverted) {
return false;
}
if(honda_static_validate_reverse_packet(packet, &decoded)) {
honda_static_decoder_commit(instance, &decoded);
return true;
}
return false;
}
static void honda_static_decoder_commit(
SubGhzProtocolDecoderHondaStatic* instance,
const HondaStaticFields* decoded) {
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(decoded);
instance->generic.serial = decoded->serial;
instance->generic.cnt = decoded->counter;
instance->generic.btn = decoded->button;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
uint8_t packet[8];
honda_static_build_packet_bytes(&instance->decoded, packet);
size_t index = 0U;
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
instance->encoder.upload[index++] =
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
}
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
instance->encoder.upload[index++] =
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
instance->encoder.upload[index++] =
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
}
const bool last_bit = (packet[7] & 1U) != 0U;
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
instance->encoder.front = 0U;
instance->encoder.size_upload = index;
}
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
FuriString* tmp = furi_string_alloc();
if(!tmp) return false;
bool ok = false;
do {
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
const char* key_str = furi_string_get_cstr(tmp);
uint64_t key = 0;
size_t hex_pos = 0;
for(size_t i = 0; key_str[i] && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9')
nibble = c - '0';
else if(c >= 'A' && c <= 'F')
nibble = c - 'A' + 10;
else if(c >= 'a' && c <= 'f')
nibble = c - 'a' + 10;
else
break;
key = (key << 4) | nibble;
hex_pos++;
}
if(hex_pos != 16) break;
*out_key = key;
ok = true;
} while(false);
furi_string_free(tmp);
return ok;
}
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
.alloc = subghz_protocol_decoder_honda_static_alloc,
.free = subghz_protocol_decoder_honda_static_free,
.feed = subghz_protocol_decoder_honda_static_feed,
.reset = subghz_protocol_decoder_honda_static_reset,
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
.serialize = subghz_protocol_decoder_honda_static_serialize,
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
.get_string = subghz_protocol_decoder_honda_static_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
.alloc = subghz_protocol_encoder_honda_static_alloc,
.free = subghz_protocol_encoder_honda_static_free,
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
.stop = subghz_protocol_encoder_honda_static_stop,
.yield = subghz_protocol_encoder_honda_static_yield,
};
const SubGhzProtocol honda_static_protocol = {
.name = HONDA_STATIC_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_honda_static_decoder,
.encoder = &subghz_protocol_honda_static_encoder,
};
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 3U;
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void subghz_protocol_encoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0U;
do {
FuriString* pstr = furi_string_alloc();
if(!pstr) break;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
furi_string_free(pstr);
break;
}
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
furi_string_free(pstr);
break;
}
furi_string_free(pstr);
uint64_t key = 0;
if(!honda_static_read_hex_u64(flipper_format, &key)) {
break;
}
honda_static_unpack_compact(key, &instance->decoded);
uint32_t serial = instance->decoded.serial;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
instance->decoded.serial = serial;
}
uint32_t btn_u32 = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
uint8_t b = (uint8_t)btn_u32;
if(honda_static_is_valid_button(b)) {
instance->decoded.button = b;
} else if(b >= 2U && b <= 5U) {
instance->decoded.button = honda_static_encoder_remap_button(b);
}
}
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
instance->decoded.counter = cnt & 0x00FFFFFFU;
}
instance->generic.serial = instance->decoded.serial;
instance->generic.cnt = instance->decoded.counter;
instance->generic.btn = instance->decoded.button;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
instance->generic.data = honda_static_pack_compact(&instance->decoded);
uint8_t key_data[8];
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
flipper_format_rewind(flipper_format);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
status = SubGhzProtocolStatusErrorParserKey;
break;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 3U;
}
honda_static_build_upload(instance);
instance->encoder.is_running = true;
status = SubGhzProtocolStatusOk;
} while(false);
return status;
}
void subghz_protocol_encoder_honda_static_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderHondaStatic* instance = context;
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0U;
}
return current;
}
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
furi_check(instance);
memset(instance, 0, sizeof(*instance));
instance->base.protocol = &honda_static_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_honda_static_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
free(instance);
}
void subghz_protocol_decoder_honda_static_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->symbols_count = 0U;
}
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint8_t sym = honda_static_level_u8(level);
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
}
return;
}
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
instance->symbols_count++;
}
return;
}
const uint16_t sc = instance->symbols_count;
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
if(!honda_static_parse_symbols(instance, true)) {
honda_static_parse_symbols(instance, false);
}
}
instance->symbols_count = 0U;
}
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
const uint64_t data = instance->generic.data;
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
}
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
furi_string_printf(
output,
"%s\r\n"
"Key:%016llX\r\n"
"Btn:%s\r\n"
"Ser:%07lX Cnt:%06lX",
instance->generic.protocol_name,
(unsigned long long)instance->generic.data,
honda_static_button_name(decoded.button),
(unsigned long)decoded.serial,
(unsigned long)decoded.counter);
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
uint32_t temp = decoded.serial;
if(!flipper_format_write_uint32(flipper_format, "Serial", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.button;
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.counter;
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
temp = decoded.checksum;
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return status;
}
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderHondaStatic* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
if(status != SubGhzProtocolStatusOk) {
return status;
}
flipper_format_rewind(flipper_format);
HondaStaticFields decoded;
honda_static_unpack_compact(instance->generic.data, &decoded);
uint32_t s = 0;
uint32_t b = 0;
uint32_t c = 0;
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
decoded.serial = s;
}
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
decoded.button = (uint8_t)b;
}
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
decoded.counter = c & 0x00FFFFFFU;
}
instance->generic.data = honda_static_pack_compact(&decoded);
instance->generic.serial = decoded.serial;
instance->generic.cnt = decoded.counter;
instance->generic.btn = decoded.button;
return status;
}
+36
View File
@@ -0,0 +1,36 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <flipper_format/flipper_format.h>
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
extern const SubGhzProtocol honda_static_protocol;
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_static_free(void* context);
void subghz_protocol_decoder_honda_static_reset(void* context);
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_honda_static_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_honda_static_stop(void* context);
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
+391
View File
@@ -0,0 +1,391 @@
#include "honda_v1.h"
#include <string.h>
#define HONDA_V1_BIT_COUNT 68U
#define HONDA_V1_VALID_BIT_COUNT_MAX 75U
#define HONDA_V1_TE_SHORT 1000U
#define HONDA_V1_TE_LONG 2000U
#define HONDA_V1_TE_DELTA 400U
#define HONDA_V1_TE_SHORT_MIN 600U
#define HONDA_V1_TE_END 3500U
typedef enum {
HondaV1DecoderStepReset = 0,
HondaV1DecoderStepPreamble = 1,
HondaV1DecoderStepData = 2,
} HondaV1DecoderStep;
typedef struct SubGhzProtocolDecoderHondaV1 {
SubGhzProtocolDecoderBase base;
uint32_t _reserved_0c;
uint32_t parser_step;
uint8_t _reserved_14[0x10];
SubGhzBlockGeneric generic;
uint32_t key_2;
uint16_t packet_bit_count;
uint8_t _reserved_5a;
uint8_t _reserved_5b;
uint32_t pending_duration;
bool pending_duration_valid;
uint8_t preamble_count;
bool data_pending;
bool last_level;
uint8_t bits[0x0C];
uint8_t bit_count;
} SubGhzProtocolDecoderHondaV1;
static const char* const honda_v1_button_names[11] = {
"Unlock",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Unknown",
"Lock",
"Trunk",
"Panic",
};
static bool honda_v1_button_valid(uint8_t button) {
return ((0x701U >> button) & 1U) != 0U;
}
static const char* honda_v1_button_name(uint8_t button) {
if(button < COUNT_OF(honda_v1_button_names)) {
return honda_v1_button_names[button];
}
return "Unknown";
}
static uint64_t honda_v1_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t value = 0;
for(size_t i = 0; i < 8; i++) {
value = (value << 8U) | bytes[i];
}
return value;
}
static void honda_v1_parse_generic_fields(SubGhzBlockGeneric* generic) {
const uint32_t low = (uint32_t)(generic->data & 0xFFFFFFFFULL);
const uint32_t high = (uint32_t)(generic->data >> 32U);
generic->cnt = high >> 4U;
generic->btn = (uint8_t)((low >> 28U) & 0x0FU);
generic->serial = low & 0xFFFFU;
generic->data_count_bit = HONDA_V1_BIT_COUNT;
}
static void honda_v1_crc_candidates(uint16_t serial, uint8_t* first, uint8_t* second) {
uint8_t base = (uint8_t)((((~(serial >> 6U)) << 2U) & 0x04U) | ((serial >> 3U) & 0x01U));
uint8_t value = (uint8_t)((((~(serial >> 4U)) & 0x01U) | (((serial >> 5U) & 0x01U) << 1U)) ^
(serial & 0x07U));
*first = (uint8_t)((value + base) & 0x07U);
*second = (uint8_t)(((value + (base ^ 0x01U)) & 0x07U) | 0x08U);
}
static void honda_v1_add_bit(SubGhzProtocolDecoderHondaV1* instance, bool bit) {
if(instance->bit_count > HONDA_V1_VALID_BIT_COUNT_MAX) {
return;
}
if(bit) {
const uint8_t byte_index = instance->bit_count >> 3U;
const uint8_t shift = ((uint8_t)~instance->bit_count) & 0x07U;
instance->bits[byte_index] |= (uint8_t)(1U << shift);
}
instance->bit_count++;
}
static void honda_v1_reset_state_(SubGhzProtocolDecoderHondaV1* instance) {
instance->parser_step = HondaV1DecoderStepReset;
instance->preamble_count = 0;
instance->data_pending = 0;
instance->bit_count = 0;
memset(instance->bits, 0, sizeof(instance->bits));
}
static bool honda_v1_duration_is(uint32_t duration, uint32_t target) {
return (duration >= target) ? ((duration - target) <= HONDA_V1_TE_DELTA) :
((target - duration) <= HONDA_V1_TE_DELTA);
}
static bool honda_v1_decoder_commit(SubGhzProtocolDecoderHondaV1* instance) {
if(instance->bit_count <= 0x43U) {
return false;
}
uint8_t shift_count = instance->bit_count - HONDA_V1_BIT_COUNT;
if(shift_count < 1U) {
shift_count = 1U;
}
for(uint8_t shift = 0; shift < shift_count; shift++) {
for(size_t i = 0; i < (sizeof(instance->bits) - 1U); i++) {
instance->bits[i] = (uint8_t)((instance->bits[i] << 1U) | (instance->bits[i + 1U] >> 7U));
}
instance->bits[sizeof(instance->bits) - 1U] <<= 1U;
}
const uint8_t button = instance->bits[4] >> 4U;
if(!honda_v1_button_valid(button)) {
return false;
}
instance->generic.data = honda_v1_bytes_to_u64_be(instance->bits);
instance->generic.data_2 = (uint8_t)(instance->bits[8] >> 4U);
instance->packet_bit_count = HONDA_V1_BIT_COUNT;
honda_v1_parse_generic_fields(&instance->generic);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
return true;
}
static void honda_v1_decoder_process_symbol(
SubGhzProtocolDecoderHondaV1* instance,
bool level,
uint32_t duration) {
const bool short_symbol = honda_v1_duration_is(duration, HONDA_V1_TE_SHORT);
const bool long_symbol = honda_v1_duration_is(duration, HONDA_V1_TE_LONG);
if(!short_symbol && !long_symbol) {
if(!level && (duration > HONDA_V1_TE_END) &&
(instance->parser_step == HondaV1DecoderStepData)) {
honda_v1_decoder_commit(instance);
}
honda_v1_reset_state_(instance);
return;
}
if(instance->parser_step == HondaV1DecoderStepReset) {
if(level) {
instance->parser_step = HondaV1DecoderStepPreamble;
instance->preamble_count = 1U;
instance->last_level = level;
}
return;
}
if(instance->parser_step == HondaV1DecoderStepPreamble) {
if(long_symbol) {
instance->preamble_count++;
instance->last_level = level;
return;
}
if(short_symbol && (instance->preamble_count > 5U)) {
instance->parser_step = HondaV1DecoderStepData;
instance->bit_count = 0U;
memset(instance->bits, 0, sizeof(instance->bits));
instance->data_pending = true;
instance->last_level = level;
return;
}
honda_v1_reset_state_(instance);
return;
}
if(short_symbol) {
if(instance->data_pending) {
honda_v1_add_bit(instance, level);
instance->data_pending = false;
instance->last_level = level;
return;
}
instance->data_pending = true;
instance->last_level = level;
} else {
if(instance->data_pending) {
honda_v1_add_bit(instance, level);
} else {
honda_v1_add_bit(instance, instance->last_level);
}
instance->last_level = level;
}
}
const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
.alloc = subghz_protocol_decoder_honda_v1_alloc,
.free = subghz_protocol_decoder_honda_v1_free,
.feed = subghz_protocol_decoder_honda_v1_feed,
.reset = subghz_protocol_decoder_honda_v1_reset,
.get_hash_data = subghz_protocol_decoder_honda_v1_get_hash_data,
.get_string = subghz_protocol_decoder_honda_v1_get_string,
.serialize = subghz_protocol_decoder_honda_v1_serialize,
.deserialize = subghz_protocol_decoder_honda_v1_deserialize,
};
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol subghz_protocol_honda_v1 = {
.name = HONDA_V1_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load,
.encoder = &subghz_protocol_honda_v1_encoder,
.decoder = &subghz_protocol_honda_v1_decoder,
};
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderHondaV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderHondaV1));
furi_check(instance);
instance->base.protocol = &subghz_protocol_honda_v1;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_honda_v1_free(void* context) {
furi_check(context);
free(context);
}
void subghz_protocol_decoder_honda_v1_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
instance->pending_duration = 0;
instance->pending_duration_valid = false;
honda_v1_reset_state_(instance);
instance->last_level = false;
}
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
if(duration < HONDA_V1_TE_DELTA) {
instance->pending_duration += duration;
instance->pending_duration_valid = true;
return;
}
if(instance->pending_duration_valid) {
const uint32_t pending = instance->pending_duration;
if(level) {
instance->pending_duration = pending + duration;
instance->pending_duration_valid = true;
return;
}
if(pending >= HONDA_V1_TE_SHORT_MIN) {
honda_v1_decoder_process_symbol(instance, true, pending);
}
instance->pending_duration = 0;
instance->pending_duration_valid = false;
}
if(level) {
instance->pending_duration = duration;
instance->pending_duration_valid = true;
return;
}
honda_v1_decoder_process_symbol(instance, false, duration);
}
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
return (uint8_t)(instance->generic.data >> 40U);
}
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
honda_v1_parse_generic_fields(&instance->generic);
uint8_t candidate_a = 0;
uint8_t candidate_b = 0;
honda_v1_crc_candidates((uint16_t)instance->generic.serial, &candidate_a, &candidate_b);
const uint8_t key_2 = instance->generic.data_2 & 0x0FU;
const bool crc_ok = (key_2 == candidate_a) || (key_2 == candidate_b);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%02X [%s]\r\n"
"Cnt:%04lX CRC:%01X [%s]",
instance->generic.protocol_name,
instance->packet_bit_count ? instance->packet_bit_count : HONDA_V1_BIT_COUNT,
instance->generic.data,
instance->generic.serial,
instance->generic.btn,
honda_v1_button_name(instance->generic.btn),
instance->generic.cnt,
key_2,
crc_ok ? "OK" : "ERR");
}
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
honda_v1_parse_generic_fields(&instance->generic);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(!flipper_format_rewind(flipper_format)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t key_2 = instance->generic.data_2 & 0x0FU;
if(!flipper_format_update_uint32(flipper_format, "Key_2", &key_2, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return status;
}
SubGhzProtocolStatus
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderHondaV1* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(!flipper_format_rewind(flipper_format)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t key_2 = 0;
if(!flipper_format_read_uint32(flipper_format, "Key_2", &key_2, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
instance->generic.data_2 = key_2 & 0x0FU;
instance->generic.protocol_name = instance->base.protocol->name;
instance->packet_bit_count = instance->generic.data_count_bit;
honda_v1_parse_generic_fields(&instance->generic);
return status;
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#define HONDA_V1_PROTOCOL_NAME "Honda V1"
typedef struct SubGhzProtocolDecoderHondaV1 SubGhzProtocolDecoderHondaV1;
extern const SubGhzProtocol subghz_protocol_honda_v1;
extern const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder;
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_honda_v1_free(void* context);
void subghz_protocol_decoder_honda_v1_reset(void* context);
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context);
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output);
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
+597
View File
@@ -0,0 +1,597 @@
#include "kia_v0.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolKiaV0"
static const SubGhzBlockConst subghz_protocol_kia_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 61,
};
struct SubGhzProtocolDecoderKIA {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
};
struct SubGhzProtocolEncoderKIA {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
KIADecoderStepReset = 0,
KIADecoderStepCheckPreambula,
KIADecoderStepSaveDuration,
KIADecoderStepCheckDuration,
} KIADecoderStep;
const SubGhzProtocolDecoder subghz_protocol_kia_decoder = {
.alloc = subghz_protocol_decoder_kia_alloc,
.free = subghz_protocol_decoder_kia_free,
.feed = subghz_protocol_decoder_kia_feed,
.reset = subghz_protocol_decoder_kia_reset,
.get_hash_data = subghz_protocol_decoder_kia_get_hash_data,
.serialize = subghz_protocol_decoder_kia_serialize,
.deserialize = subghz_protocol_decoder_kia_deserialize,
.get_string = subghz_protocol_decoder_kia_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_kia_encoder = {
.alloc = subghz_protocol_encoder_kia_alloc,
.free = subghz_protocol_encoder_kia_free,
.deserialize = subghz_protocol_encoder_kia_deserialize,
.stop = subghz_protocol_encoder_kia_stop,
.yield = subghz_protocol_encoder_kia_yield,
};
const SubGhzProtocol subghz_protocol_kia_v0 = {
.name = SUBGHZ_PROTOCOL_KIA_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_decoder,
.encoder = &subghz_protocol_kia_encoder,
};
/**
* CRC8 calculation for Kia protocol
* Polynomial: 0x7F
* Initial value: 0x00
* MSB-first processing
*/
static uint8_t kia_crc8(uint8_t* data, size_t len) {
uint8_t crc = 0x00;
for(size_t i = 0; i < len; i++) {
crc ^= data[i];
for(size_t j = 0; j < 8; j++) {
if((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ 0x7F);
else
crc <<= 1;
}
}
return crc;
}
/**
* Calculate CRC for the Kia data packet
* CRC is calculated over bits 8-55 (6 bytes)
*/
static uint8_t kia_calculate_crc(uint64_t data) {
uint8_t crc_data[6];
crc_data[0] = (data >> 48) & 0xFF;
crc_data[1] = (data >> 40) & 0xFF;
crc_data[2] = (data >> 32) & 0xFF;
crc_data[3] = (data >> 24) & 0xFF;
crc_data[4] = (data >> 16) & 0xFF;
crc_data[5] = (data >> 8) & 0xFF;
return kia_crc8(crc_data, 6);
}
/**
* Verify CRC of received data
*/
static bool kia_verify_crc(uint64_t data) {
uint8_t received_crc = data & 0xFF;
uint8_t calculated_crc = kia_calculate_crc(data);
return (received_crc == calculated_crc);
}
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKIA* instance = malloc(sizeof(SubGhzProtocolEncoderKIA));
instance->base.protocol = &subghz_protocol_kia_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.size_upload = 848;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.repeat = 1;
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_kia_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
free(instance->encoder.upload);
free(instance);
}
void subghz_protocol_encoder_kia_stop(void* context) {
SubGhzProtocolEncoderKIA* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_kia_yield(void* context) {
SubGhzProtocolEncoderKIA* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance);
/**
* Generating an upload from data.
* @param instance Pointer to a SubGhzProtocolEncoderKIA instance
* @return true On success
*/
static bool subghz_protocol_encoder_kia_get_upload(SubGhzProtocolEncoderKIA* instance) {
furi_assert(instance);
// Save original button
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(4);
size_t index = 0;
size_t size_upload = (instance->generic.data_count_bit * 2 + 32) * 2 + 540;
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(
TAG,
"Size upload exceeds allocated encoder buffer. %i",
instance->generic.data_count_bit);
return false;
} else {
instance->encoder.size_upload = size_upload;
}
// Counter increment logic
if(instance->generic.cnt < 0xFFFF) {
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
} else if(instance->generic.cnt >= 0xFFFF) {
instance->generic.cnt = 0;
}
// Get button (custom or original)
// This allows button changing with directional keys in SubGhz app
uint8_t btn = subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK ?
subghz_custom_btn_get_original() :
subghz_custom_btn_get();
// Update the generic button value for potential button changes
instance->generic.btn = btn;
// Build data packet
uint64_t data = 0;
// Bits 56-59: Fixed preamble (0x0F)
data |= ((uint64_t)(0x0F) << 56);
// Bits 40-55: Counter (16 bits)
data |= ((uint64_t)(instance->generic.cnt & 0xFFFF) << 40);
// Bits 12-39: Serial (28 bits)
data |= ((uint64_t)(instance->generic.serial & 0x0FFFFFFF) << 12);
// Bits 8-11: Button (4 bits)
data |= ((uint64_t)(btn & 0x0F) << 8);
// Bits 0-7: CRC
uint8_t crc = kia_calculate_crc(data);
data |= crc;
instance->generic.data = data;
// Send header (270 pulses of te_short)
for(uint16_t i = 270; i > 0; i--) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
}
// Send 2 data bursts
for(uint8_t h = 2; h > 0; h--) {
// Send sync bits (15 pulses of te_short)
for(uint8_t i = 15; i > 0; i--) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
}
// Send data bits (PWM encoding)
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
// Send bit 1: long pulse
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_long);
} else {
// Send bit 0: short pulse
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
}
}
// Send stop bit (3x te_long)
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_long * 3);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_long * 3);
}
return true;
}
SubGhzProtocolStatus subghz_protocol_encoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_kia_const.min_count_bit_for_found);
if(ret != SubGhzProtocolStatusOk) {
break;
}
// Extract serial, button, counter from data
subghz_protocol_kia_check_remote_controller(&instance->generic);
// Verify CRC
if(!kia_verify_crc(instance->generic.data)) {
FURI_LOG_W(TAG, "CRC mismatch in loaded file");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
if(!subghz_protocol_encoder_kia_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
// Update the Key in the file with the new counter/button/CRC
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
FURI_LOG_E(TAG, "Unable to update Key");
ret = SubGhzProtocolStatusErrorParserKey;
break;
}
instance->encoder.is_running = true;
} while(false);
return ret;
}
// ============================================================================
// ENCODER HELPER FUNCTIONS
// ============================================================================
void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
instance->generic.btn = button & 0x0F;
}
void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
instance->generic.cnt = counter;
}
void subghz_protocol_encoder_kia_increment_counter(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
if(instance->generic.cnt < 0xFFFF) {
instance->generic.cnt++;
} else {
instance->generic.cnt = 0;
}
}
uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
return instance->generic.cnt;
}
uint8_t subghz_protocol_encoder_kia_get_button(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKIA* instance = context;
return instance->generic.btn;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKIA* instance = malloc(sizeof(SubGhzProtocolDecoderKIA));
instance->base.protocol = &subghz_protocol_kia_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_kia_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
free(instance);
}
void subghz_protocol_decoder_kia_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
instance->decoder.parser_step = KIADecoderStepReset;
}
void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
switch(instance->decoder.parser_step) {
case KIADecoderStepReset:
if((level) && (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta)) {
instance->decoder.parser_step = KIADecoderStepCheckPreambula;
instance->decoder.te_last = duration;
instance->header_count = 0;
}
break;
case KIADecoderStepCheckPreambula:
if(level) {
if((DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta) ||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
subghz_protocol_kia_const.te_delta)) {
instance->decoder.te_last = duration;
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
} else if(
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta) &&
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta)) {
// Found header
instance->header_count++;
break;
} else if(
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
subghz_protocol_kia_const.te_delta) &&
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) <
subghz_protocol_kia_const.te_delta)) {
// Found start bit
if(instance->header_count > 15) {
instance->decoder.parser_step = KIADecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 1;
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
break;
case KIADecoderStepSaveDuration:
if(level) {
if(duration >=
(subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2UL)) {
// Found stop bit
instance->decoder.parser_step = KIADecoderStepReset;
if(instance->decoder.decode_count_bit ==
subghz_protocol_kia_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
// Verify CRC before accepting the packet
if(kia_verify_crc(instance->generic.data)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
} else {
FURI_LOG_W(TAG, "CRC verification failed, packet rejected");
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = KIADecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
break;
case KIADecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
subghz_protocol_kia_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = KIADecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) <
subghz_protocol_kia_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
subghz_protocol_kia_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = KIADecoderStepSaveDuration;
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
} else {
instance->decoder.parser_step = KIADecoderStepReset;
}
break;
}
}
/**
* Analysis of received data
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance) {
/*
* 0x0F 0112 43B04EC 1 7D
* 0x0F 0113 43B04EC 1 DF
* 0x0F 0114 43B04EC 1 30
* 0x0F 0115 43B04EC 2 13
* 0x0F 0116 43B04EC 3 F5
* CNT Serial K CRC8 Kia
*/
instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF);
instance->btn = (instance->data >> 8) & 0x0F;
instance->cnt = (instance->data >> 40) & 0xFFFF;
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(4);
}
uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_kia_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
if(instance->generic.data_count_bit < subghz_protocol_kia_const.min_count_bit_for_found) {
ret = SubGhzProtocolStatusErrorParserBitCount;
}
}
return ret;
}
static const char* subghz_protocol_kia_get_name_button(uint8_t btn) {
const char* name_btn[5] = {"Unknown", "Lock", "Unlock", "Trunk", "Horn"};
return name_btn[btn < 5 ? btn : 0];
}
void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderKIA* instance = context;
subghz_protocol_kia_check_remote_controller(&instance->generic);
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint8_t received_crc = instance->generic.data & 0xFF;
uint8_t calculated_crc = kia_calculate_crc(instance->generic.data);
bool crc_valid = (received_crc == calculated_crc);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%02X:[%s]\r\n"
"CRC:%02X %s",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
instance->generic.serial,
instance->generic.cnt,
instance->generic.btn,
subghz_protocol_kia_get_name_button(instance->generic.btn),
received_crc,
crc_valid ? "(OK)" : "(FAIL)");
}
File diff suppressed because it is too large Load Diff
+887
View File
@@ -0,0 +1,887 @@
#include "kia_v3_v4.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolKiaV3V4"
#define KIA_MF_KEY 0xA8F5DFFC8DAA5CDBULL
#define KIA_V3_V4_PREAMBLE_PAIRS 16
#define KIA_V3_V4_TOTAL_BURSTS 3
#define KIA_V3_V4_INTER_BURST_GAP_US 10000
#define KIA_V3_V4_SYNC_DURATION 1200
static const char* kia_version_names[] = {"KIA/HYU V4", "KIA/HYU V3"};
static const SubGhzBlockConst subghz_protocol_kia_v3_v4_const = {
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit_for_found = 68,
};
struct SubGhzProtocolDecoderKiaV3V4 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
uint8_t raw_bits[32];
uint16_t raw_bit_count;
bool is_v3_sync;
uint32_t encrypted;
uint32_t decrypted;
uint8_t crc;
uint8_t version;
};
struct SubGhzProtocolEncoderKiaV3V4 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t serial;
uint8_t btn;
uint16_t cnt;
uint8_t version;
uint8_t crc;
uint32_t encrypted;
uint32_t decrypted;
};
typedef enum {
KiaV3V4DecoderStepReset = 0,
KiaV3V4DecoderStepCheckPreamble,
KiaV3V4DecoderStepCollectRawBits,
} KiaV3V4DecoderStep;
static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
uint32_t block = data;
uint64_t tkey = key;
for(int i = 0; i < 528; i++) {
int lutkey = ((block >> 0) & 1) | ((block >> 7) & 2) | ((block >> 17) & 4) |
((block >> 22) & 8) | ((block >> 26) & 16);
int lsb =
((block >> 31) ^ ((block >> 15) & 1) ^ ((0x3A5C742E >> lutkey) & 1) ^
((tkey >> 15) & 1));
block = ((block & 0x7FFFFFFF) << 1) | lsb;
tkey = ((tkey & 0x7FFFFFFFFFFFFFFFULL) << 1) | (tkey >> 63);
}
return block;
}
static uint32_t keeloq_common_encrypt(uint32_t data, uint64_t key) {
uint32_t block = data;
uint64_t tkey = key;
for(int i = 0; i < 528; i++) {
int lutkey = ((block >> 1) & 1) | ((block >> 8) & 2) | ((block >> 18) & 4) |
((block >> 23) & 8) | ((block >> 27) & 16);
int msb =
((block >> 0) ^ ((block >> 16) & 1) ^ ((0x3A5C742E >> lutkey) & 1) ^
((tkey >> 0) & 1));
block = ((block >> 1) & 0x7FFFFFFF) | (msb << 31);
tkey = ((tkey >> 1) & 0x7FFFFFFFFFFFFFFFULL) | ((tkey & 1) << 63);
}
return block;
}
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;
}
static void kia_v3_v4_add_raw_bit(SubGhzProtocolDecoderKiaV3V4* instance, bool bit) {
if(instance->raw_bit_count < 256) {
uint16_t byte_idx = instance->raw_bit_count / 8;
uint8_t bit_idx = 7 - (instance->raw_bit_count % 8);
if(bit) {
instance->raw_bits[byte_idx] |= (1 << bit_idx);
} else {
instance->raw_bits[byte_idx] &= ~(1 << bit_idx);
}
instance->raw_bit_count++;
}
}
static uint8_t kia_v3_v4_calculate_crc(uint8_t* bytes) {
uint8_t crc = 0;
for(int i = 0; i < 8; i++) {
crc ^= (bytes[i] & 0x0F) ^ (bytes[i] >> 4);
}
return crc & 0x0F;
}
static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
if(instance->raw_bit_count < 68) {
return false;
}
uint8_t* b = instance->raw_bits;
if(instance->is_v3_sync) {
uint16_t num_bytes = (instance->raw_bit_count + 7) / 8;
for(uint16_t i = 0; i < num_bytes; i++) {
b[i] = ~b[i];
}
}
uint8_t crc = (b[8] >> 4) & 0x0F;
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) | ((uint32_t)reverse8(b[2]) << 16) |
((uint32_t)reverse8(b[1]) << 8) | (uint32_t)reverse8(b[0]);
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) | ((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) | (uint32_t)reverse8(b[4]);
uint8_t btn = (reverse8(b[7]) & 0xF0) >> 4;
uint8_t our_serial_lsb = serial & 0xFF;
uint32_t decrypted = keeloq_common_decrypt(encrypted, KIA_MF_KEY);
uint8_t dec_btn = (decrypted >> 28) & 0x0F;
uint8_t dec_serial_lsb = (decrypted >> 16) & 0xFF;
if(dec_btn != btn || dec_serial_lsb != our_serial_lsb) {
return false;
}
instance->encrypted = encrypted;
instance->decrypted = decrypted;
instance->crc = crc;
instance->generic.serial = serial;
instance->generic.btn = btn;
instance->generic.cnt = decrypted & 0xFFFF;
instance->version = instance->is_v3_sync ? 1 : 0;
uint64_t key_data = ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) | ((uint64_t)b[2] << 40) |
((uint64_t)b[3] << 32) | ((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
((uint64_t)b[6] << 8) | (uint64_t)b[7];
instance->generic.data = key_data;
instance->generic.data_count_bit = 68;
instance->decoder.decode_data = key_data;
instance->decoder.decode_count_bit = 68;
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(5);
return true;
}
const SubGhzProtocolDecoder subghz_protocol_kia_v3_v4_decoder = {
.alloc = subghz_protocol_decoder_kia_v3_v4_alloc,
.free = subghz_protocol_decoder_kia_v3_v4_free,
.feed = subghz_protocol_decoder_kia_v3_v4_feed,
.reset = subghz_protocol_decoder_kia_v3_v4_reset,
.get_hash_data = subghz_protocol_decoder_kia_v3_v4_get_hash_data,
.serialize = subghz_protocol_decoder_kia_v3_v4_serialize,
.deserialize = subghz_protocol_decoder_kia_v3_v4_deserialize,
.get_string = subghz_protocol_decoder_kia_v3_v4_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
.alloc = subghz_protocol_encoder_kia_v3_v4_alloc,
.free = subghz_protocol_encoder_kia_v3_v4_free,
.deserialize = subghz_protocol_encoder_kia_v3_v4_deserialize,
.stop = subghz_protocol_encoder_kia_v3_v4_stop,
.yield = subghz_protocol_encoder_kia_v3_v4_yield,
};
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v3_v4_decoder,
.encoder = &subghz_protocol_kia_v3_v4_encoder,
};
static const char* subghz_protocol_kia_v3_v4_get_name_button(uint8_t btn) {
switch(btn) {
case 0x1: return "Lock";
case 0x2: return "Unlock";
case 0x3: return "Trunk";
case 0x4: return "Panic";
case 0x8: return "Horn";
default: return "Unknown";
}
}
void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
instance->base.protocol = &subghz_protocol_kia_v3_v4;
instance->generic.protocol_name = instance->base.protocol->name;
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
instance->version = 0;
instance->encoder.size_upload = 600;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.repeat = 10;
instance->encoder.front = 0;
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
static void subghz_protocol_encoder_kia_v3_v4_build_packet(
SubGhzProtocolEncoderKiaV3V4* instance,
uint8_t* raw_bytes) {
uint32_t plaintext = (instance->cnt & 0xFFFF) | ((instance->serial & 0xFF) << 16) |
(0x1 << 24) | ((instance->btn & 0x0F) << 28);
instance->decrypted = plaintext;
uint32_t encrypted = keeloq_common_encrypt(plaintext, KIA_MF_KEY);
instance->encrypted = encrypted;
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF);
raw_bytes[1] = reverse8((encrypted >> 8) & 0xFF);
raw_bytes[2] = reverse8((encrypted >> 16) & 0xFF);
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF);
uint32_t serial_btn = (instance->serial & 0x0FFFFFFF) |
((uint32_t)(instance->btn & 0x0F) << 28);
raw_bytes[4] = reverse8((serial_btn >> 0) & 0xFF);
raw_bytes[5] = reverse8((serial_btn >> 8) & 0xFF);
raw_bytes[6] = reverse8((serial_btn >> 16) & 0xFF);
raw_bytes[7] = reverse8((serial_btn >> 24) & 0xFF);
uint8_t crc = kia_v3_v4_calculate_crc(raw_bytes);
raw_bytes[8] = (crc << 4);
instance->crc = crc;
instance->generic.data = ((uint64_t)raw_bytes[0] << 56) | ((uint64_t)raw_bytes[1] << 48) |
((uint64_t)raw_bytes[2] << 40) | ((uint64_t)raw_bytes[3] << 32) |
((uint64_t)raw_bytes[4] << 24) | ((uint64_t)raw_bytes[5] << 16) |
((uint64_t)raw_bytes[6] << 8) | (uint64_t)raw_bytes[7];
instance->generic.data_count_bit = 68;
}
static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
furi_assert(instance);
uint8_t raw_bytes[9];
subghz_protocol_encoder_kia_v3_v4_build_packet(instance, raw_bytes);
if(instance->version == 1) {
for(int i = 0; i < 9; i++) {
raw_bytes[i] = ~raw_bytes[i];
}
}
size_t index = 0;
for(uint8_t burst = 0; burst < KIA_V3_V4_TOTAL_BURSTS; burst++) {
if(burst > 0) {
instance->encoder.upload[index++] =
level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
}
for(int i = 0; i < KIA_V3_V4_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
}
if(instance->version == 0) {
instance->encoder.upload[index++] = level_duration_make(true, KIA_V3_V4_SYNC_DURATION);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, KIA_V3_V4_SYNC_DURATION);
}
for(int byte_idx = 0; byte_idx < 9; byte_idx++) {
int bits_in_byte = (byte_idx == 8) ? 4 : 8;
for(int bit_idx = 7; bit_idx >= (8 - bits_in_byte); bit_idx--) {
bool bit = (raw_bytes[byte_idx] >> bit_idx) & 1;
if(bit) {
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
} else {
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_long);
}
}
}
}
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
SubGhzProtocolStatus
subghz_protocol_encoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
flipper_format_rewind(flipper_format);
do {
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
furi_string_free(temp_str);
break;
}
const char* proto_str = furi_string_get_cstr(temp_str);
if(!furi_string_equal(temp_str, instance->base.protocol->name) &&
strcmp(proto_str, "KIA/HYU V3") != 0 && strcmp(proto_str, "KIA/HYU V4") != 0) {
furi_string_free(temp_str);
break;
}
bool version_from_protocol_name = false;
if(strcmp(proto_str, "KIA/HYU V3") == 0) {
instance->version = 1;
version_from_protocol_name = true;
} else if(strcmp(proto_str, "KIA/HYU V4") == 0) {
instance->version = 0;
version_from_protocol_name = true;
}
furi_string_free(temp_str);
flipper_format_rewind(flipper_format);
uint32_t bit_count_temp;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
break;
}
instance->generic.data_count_bit = 68;
flipper_format_rewind(flipper_format);
temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
furi_string_free(temp_str);
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
} else if(c >= 'A' && c <= 'F') {
nibble = c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
uint8_t b[8];
b[0] = (key >> 56) & 0xFF;
b[1] = (key >> 48) & 0xFF;
b[2] = (key >> 40) & 0xFF;
b[3] = (key >> 32) & 0xFF;
b[4] = (key >> 24) & 0xFF;
b[5] = (key >> 16) & 0xFF;
b[6] = (key >> 8) & 0xFF;
b[7] = key & 0xFF;
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
} else {
}
instance->generic.serial = instance->serial;
flipper_format_rewind(flipper_format);
uint32_t btn_temp;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->btn = (uint8_t)btn_temp;
} else {
uint8_t b7 = instance->generic.data & 0xFF;
instance->btn = (reverse8(b7) & 0xF0) >> 4;
}
flipper_format_rewind(flipper_format);
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->cnt = (uint16_t)cnt_temp;
} else {
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp;
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
instance->cnt = decrypted_temp & 0xFFFF;
} else {
instance->cnt = 0;
}
}
flipper_format_rewind(flipper_format);
uint32_t version_temp;
if(flipper_format_read_uint32(flipper_format, "Version", &version_temp, 1)) {
if(!version_from_protocol_name) {
instance->version = (uint8_t)version_temp;
}
} else if(!version_from_protocol_name) {
instance->version = 0;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 10;
}
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(5);
uint8_t selected_btn;
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
selected_btn = subghz_custom_btn_get_original();
} else {
selected_btn = subghz_custom_btn_get();
}
if(selected_btn == 5) {
instance->btn = 0x8;
} else if(selected_btn >= 1 && selected_btn <= 4) {
instance->btn = selected_btn;
}
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
instance->cnt = (instance->cnt + mult) & 0xFFFF;
instance->generic.btn = instance->btn;
instance->generic.cnt = instance->cnt;
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t cnt_to_write = instance->cnt;
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t btn_to_write = instance->btn;
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t encrypted_to_write = instance->encrypted;
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t crc_to_write = instance->crc;
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
}
instance->encoder.is_running = true;
instance->encoder.front = 0;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_kia_v3_v4_stop(void* context) {
if(!context) return;
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
}
LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
SubGhzProtocolEncoderKiaV3V4* instance = context;
if(!instance || !instance->encoder.upload || instance->encoder.repeat == 0 ||
!instance->encoder.is_running) {
if(instance) {
instance->encoder.is_running = false;
}
return level_duration_reset();
}
if(instance->encoder.front >= instance->encoder.size_upload) {
instance->encoder.is_running = false;
instance->encoder.front = 0;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV3V4));
instance->base.protocol = &subghz_protocol_kia_v3_v4;
instance->generic.protocol_name = instance->base.protocol->name;
instance->version = 0;
instance->is_v3_sync = false;
return instance;
}
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
free(instance);
}
void subghz_protocol_decoder_kia_v3_v4_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
instance->header_count = 0;
instance->raw_bit_count = 0;
instance->is_v3_sync = false;
instance->crc = 0;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
}
void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
switch(instance->decoder.parser_step) {
case KiaV3V4DecoderStepReset:
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
instance->decoder.te_last = duration;
instance->header_count = 1;
}
break;
case KiaV3V4DecoderStepCheckPreamble:
if(level) {
if(DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
instance->decoder.te_last = duration;
instance->header_count++;
} else if(duration > 1000 && duration < 1500) {
if(instance->header_count >= 8) {
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
instance->raw_bit_count = 0;
instance->is_v3_sync = false;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
} else {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
} else {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
} else {
if(duration > 1000 && duration < 1500) {
if(instance->header_count >= 8) {
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
instance->raw_bit_count = 0;
instance->is_v3_sync = true;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
} else {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta &&
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
instance->header_count++;
} else if(duration > 1500) {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
}
break;
case KiaV3V4DecoderStepCollectRawBits:
if(level) {
if(duration > 1000 && duration < 1500) {
if(kia_v3_v4_process_buffer(instance)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
kia_v3_v4_add_raw_bit(instance, false);
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_long) <
subghz_protocol_kia_v3_v4_const.te_delta) {
kia_v3_v4_add_raw_bit(instance, true);
} else {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
} else {
if(duration > 1000 && duration < 1500) {
if(kia_v3_v4_process_buffer(instance)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
} else if(duration > 1500) {
if(kia_v3_v4_process_buffer(instance)) {
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
}
break;
}
}
uint8_t subghz_protocol_decoder_kia_v3_v4_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp = instance->version;
if(!flipper_format_write_uint32(flipper_format, "Version", &temp, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp = instance->crc;
if(!flipper_format_write_uint32(flipper_format, "CRC", &temp, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
if(instance->generic.data_count_bit < 64) {
ret = SubGhzProtocolStatusErrorParserBitCount;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
instance->decrypted = 0;
}
uint32_t temp_version = 0;
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
instance->version = temp_version;
} else {
instance->version = 0;
}
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
} else {
instance->crc = 0;
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
}
if(instance->generic.data != 0) {
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
}
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
}
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(5);
}
return ret;
}
static uint64_t compute_yek(uint64_t key) {
uint64_t yek = 0;
for(int i = 0; i < 64; i++) {
yek |= ((key >> i) & 1) << (63 - i);
}
return yek;
}
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
uint8_t bytes[8];
for(int i = 0; i < 8; i++) {
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
return (calculated_crc == received_crc);
}
void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
uint64_t yek = compute_yek(instance->generic.data);
uint32_t key_hi = (uint32_t)(instance->generic.data >> 32);
uint32_t key_lo = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
uint32_t yek_hi = (uint32_t)(yek >> 32);
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Sn:%07lX Btn:%X [%s]\r\n"
"Dec:%08lX Cnt:%04lX\r\n"
"CRC:%X %s",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
instance->decrypted,
instance->generic.cnt,
instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
}
+334 -254
View File
@@ -1,5 +1,4 @@
#include "kia_v3_v4.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
@@ -7,25 +6,25 @@
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolKiaV3V4"
#define KIA_MF_KEY 0xA8F5DFFC8DAA5CDBULL
#define TAG "KiaV3V4"
static const char* kia_version_names[] = {"Kia V4", "Kia V3"};
#define KIA_V3_V4_PREAMBLE_PAIRS 16
#define KIA_V3_V4_TOTAL_BURSTS 3
#define KIA_V3_V4_INTER_BURST_GAP_US 10000
#define KIA_V3_V4_SYNC_DURATION 1200
static const char* kia_version_names[] = {"KIA/HYU V4", "KIA/HYU V3"};
static const SubGhzBlockConst subghz_protocol_kia_v3_v4_const = {
static const SubGhzBlockConst kia_protocol_v3_v4_const = {
.te_short = 400,
.te_long = 800,
.te_delta = 150,
.min_count_bit_for_found = 68,
};
struct SubGhzProtocolDecoderKiaV3V4 {
typedef struct SubGhzProtocolDecoderKiaV3V4 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
@@ -39,9 +38,9 @@ struct SubGhzProtocolDecoderKiaV3V4 {
uint32_t decrypted;
uint8_t crc;
uint8_t version;
};
} SubGhzProtocolDecoderKiaV3V4;
struct SubGhzProtocolEncoderKiaV3V4 {
typedef struct SubGhzProtocolEncoderKiaV3V4 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
@@ -50,11 +49,10 @@ struct SubGhzProtocolEncoderKiaV3V4 {
uint8_t btn;
uint16_t cnt;
uint8_t version;
uint8_t crc;
uint32_t encrypted;
uint32_t decrypted;
};
} SubGhzProtocolEncoderKiaV3V4;
typedef enum {
KiaV3V4DecoderStepReset = 0,
@@ -62,6 +60,7 @@ typedef enum {
KiaV3V4DecoderStepCollectRawBits,
} KiaV3V4DecoderStep;
// KeeLoq decrypt
static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
uint32_t block = data;
uint64_t tkey = key;
@@ -77,6 +76,7 @@ static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
return block;
}
// KeeLoq encrypt
static uint32_t keeloq_common_encrypt(uint32_t data, uint64_t key) {
uint32_t block = data;
uint64_t tkey = key;
@@ -167,14 +167,6 @@ static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
((uint64_t)b[6] << 8) | (uint64_t)b[7];
instance->generic.data = key_data;
instance->generic.data_count_bit = 68;
instance->decoder.decode_data = key_data;
instance->decoder.decode_count_bit = 68;
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
}
subghz_custom_btn_set_max(5);
return true;
}
@@ -201,23 +193,16 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v3_v4_decoder,
.encoder = &subghz_protocol_kia_v3_v4_encoder,
};
static const char* subghz_protocol_kia_v3_v4_get_name_button(uint8_t btn) {
switch(btn) {
case 0x1: return "Lock";
case 0x2: return "Unlock";
case 0x3: return "Trunk";
case 0x4: return "Panic";
case 0x8: return "Horn";
default: return "Unknown";
}
}
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
@@ -233,15 +218,16 @@ void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
instance->encoder.size_upload = 600;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.repeat = 10;
instance->encoder.repeat = 40;
instance->encoder.front = 0;
instance->encoder.is_running = false;
FURI_LOG_I(TAG, "Encoder allocated at %p", instance);
return instance;
}
void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
furi_assert(context);
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
@@ -252,6 +238,7 @@ void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
static void subghz_protocol_encoder_kia_v3_v4_build_packet(
SubGhzProtocolEncoderKiaV3V4* instance,
uint8_t* raw_bytes) {
// Build plaintext for encryption:
uint32_t plaintext = (instance->cnt & 0xFFFF) | ((instance->serial & 0xFF) << 16) |
(0x1 << 24) | ((instance->btn & 0x0F) << 28);
@@ -260,11 +247,20 @@ static void subghz_protocol_encoder_kia_v3_v4_build_packet(
uint32_t encrypted = keeloq_common_encrypt(plaintext, KIA_MF_KEY);
instance->encrypted = encrypted;
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF);
FURI_LOG_I(
TAG,
"Encrypt: plain=0x%08lX -> enc=0x%08lX",
(unsigned long)plaintext,
(unsigned long)encrypted);
// Decoder does: encrypted = (rev(b[3])<<24) | (rev(b[2])<<16) | (rev(b[1])<<8) | rev(b[0])
// So b[0] must contain reverse8(LSB), b[3] must contain reverse8(MSB)
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF); // LSB
raw_bytes[1] = reverse8((encrypted >> 8) & 0xFF);
raw_bytes[2] = reverse8((encrypted >> 16) & 0xFF);
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF);
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF); // MSB
// Serial/button
uint32_t serial_btn = (instance->serial & 0x0FFFFFFF) |
((uint32_t)(instance->btn & 0x0F) << 28);
raw_bytes[4] = reverse8((serial_btn >> 0) & 0xFF);
@@ -272,19 +268,42 @@ static void subghz_protocol_encoder_kia_v3_v4_build_packet(
raw_bytes[6] = reverse8((serial_btn >> 16) & 0xFF);
raw_bytes[7] = reverse8((serial_btn >> 24) & 0xFF);
// CRC
uint8_t crc = kia_v3_v4_calculate_crc(raw_bytes);
raw_bytes[8] = (crc << 4);
instance->crc = crc;
// DEBUG: Log the exact raw bytes we're generating
FURI_LOG_I(
TAG,
"TX raw: %02X %02X %02X %02X %02X %02X %02X %02X %02X",
raw_bytes[0],
raw_bytes[1],
raw_bytes[2],
raw_bytes[3],
raw_bytes[4],
raw_bytes[5],
raw_bytes[6],
raw_bytes[7],
raw_bytes[8]);
// Store in generic.data for display
instance->generic.data = ((uint64_t)raw_bytes[0] << 56) | ((uint64_t)raw_bytes[1] << 48) |
((uint64_t)raw_bytes[2] << 40) | ((uint64_t)raw_bytes[3] << 32) |
((uint64_t)raw_bytes[4] << 24) | ((uint64_t)raw_bytes[5] << 16) |
((uint64_t)raw_bytes[6] << 8) | (uint64_t)raw_bytes[7];
instance->generic.data_count_bit = 68;
FURI_LOG_I(
TAG,
"Packet built: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X, CRC=0x%X",
(unsigned long)instance->serial,
instance->btn,
instance->cnt,
crc);
}
static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
furi_assert(instance);
furi_check(instance);
uint8_t raw_bytes[9];
subghz_protocol_encoder_kia_v3_v4_build_packet(instance, raw_bytes);
@@ -303,24 +322,29 @@ static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKi
level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
}
// Preamble: alternating short pulses
for(int i = 0; i < KIA_V3_V4_PREAMBLE_PAIRS; i++) {
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(true, kia_protocol_v3_v4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(false, kia_protocol_v3_v4_const.te_short);
}
// Sync pulse - different for V3 vs V4
if(instance->version == 0) {
// V4: long HIGH, short LOW
instance->encoder.upload[index++] = level_duration_make(true, KIA_V3_V4_SYNC_DURATION);
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(false, kia_protocol_v3_v4_const.te_short);
} else {
// V3: short HIGH, long LOW
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(true, kia_protocol_v3_v4_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, KIA_V3_V4_SYNC_DURATION);
}
// Data bits - PWM encoding with complementary durations
for(int byte_idx = 0; byte_idx < 9; byte_idx++) {
int bits_in_byte = (byte_idx == 8) ? 4 : 8;
@@ -328,31 +352,43 @@ static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKi
bool bit = (raw_bytes[byte_idx] >> bit_idx) & 1;
if(bit) {
// bit 1: long HIGH, short LOW (total ~1200µs)
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_long);
level_duration_make(true, kia_protocol_v3_v4_const.te_long); // 800µs
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(false, kia_protocol_v3_v4_const.te_short); // 400µs
} else {
// bit 0: short HIGH, long LOW (total ~1200µs)
instance->encoder.upload[index++] =
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
level_duration_make(true, kia_protocol_v3_v4_const.te_short); // 400µs
instance->encoder.upload[index++] =
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_long);
level_duration_make(false, kia_protocol_v3_v4_const.te_long); // 800µs
}
}
}
}
//instance->encoder.upload[index++] = level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
instance->encoder.size_upload = index;
instance->encoder.front = 0;
FURI_LOG_I(
TAG,
"Upload built: %d bursts, size_upload=%zu, version=%s",
KIA_V3_V4_TOTAL_BURSTS,
instance->encoder.size_upload,
instance->version == 0 ? "V4" : "V3");
}
SubGhzProtocolStatus
subghz_protocol_encoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->encoder.is_running = false;
instance->encoder.front = 0;
//instance->encoder.repeat = 40;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
@@ -361,39 +397,49 @@ SubGhzProtocolStatus
do {
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
furi_string_free(temp_str);
break;
}
// Accept "Kia V3/V4", "Kia V3", or "Kia V4"
const char* proto_str = furi_string_get_cstr(temp_str);
if(!furi_string_equal(temp_str, instance->base.protocol->name) &&
strcmp(proto_str, "KIA/HYU V3") != 0 && strcmp(proto_str, "KIA/HYU V4") != 0) {
strcmp(proto_str, "Kia V3") != 0 && strcmp(proto_str, "Kia V4") != 0) {
FURI_LOG_E(TAG, "Wrong protocol %s", proto_str);
furi_string_free(temp_str);
break;
}
// Set version based on protocol name if specific
bool version_from_protocol_name = false;
if(strcmp(proto_str, "KIA/HYU V3") == 0) {
if(strcmp(proto_str, "Kia V3") == 0) {
instance->version = 1;
version_from_protocol_name = true;
} else if(strcmp(proto_str, "KIA/HYU V4") == 0) {
FURI_LOG_I(TAG, "Protocol name indicates V3");
} else if(strcmp(proto_str, "Kia V4") == 0) {
instance->version = 0;
version_from_protocol_name = true;
FURI_LOG_I(TAG, "Protocol name indicates V4");
}
furi_string_free(temp_str);
// Read bit count
flipper_format_rewind(flipper_format);
uint32_t bit_count_temp;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
instance->generic.data_count_bit = 68;
// Read key data
flipper_format_rewind(flipper_format);
temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
furi_string_free(temp_str);
break;
}
@@ -415,6 +461,7 @@ SubGhzProtocolStatus
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
FURI_LOG_E(TAG, "Invalid hex character: %c", c);
break;
}
@@ -424,13 +471,10 @@ SubGhzProtocolStatus
furi_string_free(temp_str);
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles", hex_pos);
break;
}
instance->generic.data = key;
FURI_LOG_I(TAG, "Parsed key: 0x%016llX", (unsigned long long)instance->generic.data);
// Read serial
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
uint8_t b[8];
@@ -446,129 +490,89 @@ SubGhzProtocolStatus
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", (unsigned long)instance->serial);
} else {
FURI_LOG_I(TAG, "Read serial: 0x%08lX", (unsigned long)instance->serial);
}
instance->generic.serial = instance->serial;
// Read button
flipper_format_rewind(flipper_format);
uint32_t btn_temp;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->btn = (uint8_t)btn_temp;
FURI_LOG_I(TAG, "Read button: 0x%X", instance->btn);
} else {
uint8_t b7 = instance->generic.data & 0xFF;
instance->btn = (reverse8(b7) & 0xF0) >> 4;
FURI_LOG_I(TAG, "Extracted button: 0x%X", instance->btn);
}
instance->generic.btn = instance->btn;
// Read counter
flipper_format_rewind(flipper_format);
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->cnt = (uint16_t)cnt_temp;
FURI_LOG_I(TAG, "Read counter: 0x%04X", instance->cnt);
} else {
// Try Decrypted field
flipper_format_rewind(flipper_format);
uint32_t decrypted_temp;
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
instance->cnt = decrypted_temp & 0xFFFF;
FURI_LOG_I(TAG, "Extracted counter from Decrypted: 0x%04X", instance->cnt);
} else {
instance->cnt = 0;
FURI_LOG_W(TAG, "Counter not found, using 0");
}
}
instance->generic.cnt = instance->cnt;
// Read version - ONLY use file version if protocol name didn't specify one
flipper_format_rewind(flipper_format);
uint32_t version_temp;
if(flipper_format_read_uint32(flipper_format, "Version", &version_temp, 1)) {
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &version_temp, 1)) {
if(!version_from_protocol_name) {
// Generic "Kia V3/V4" protocol - use file's version
instance->version = (uint8_t)version_temp;
FURI_LOG_I(
TAG, "Read version from file: %s", instance->version == 0 ? "V4" : "V3");
} else {
// Protocol name was specific - trust that, ignore file version
FURI_LOG_I(
TAG,
"Using version from protocol name: %s (file had Version=%lu)",
instance->version == 0 ? "V4" : "V3",
(unsigned long)version_temp);
}
} else if(!version_from_protocol_name) {
instance->version = 0;
FURI_LOG_I(TAG, "Version not found, defaulting to V4");
}
// Read repeat
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 10;
instance->encoder.repeat = 40;
FURI_LOG_D(TAG, "Repeat not found, using default 40");
}
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->btn);
}
subghz_custom_btn_set_max(5);
uint8_t selected_btn;
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
selected_btn = subghz_custom_btn_get_original();
} else {
selected_btn = subghz_custom_btn_get();
}
if(selected_btn == 5) {
instance->btn = 0x8;
} else if(selected_btn >= 1 && selected_btn <= 4) {
instance->btn = selected_btn;
}
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
instance->cnt = (instance->cnt + mult) & 0xFFFF;
instance->generic.btn = instance->btn;
instance->generic.cnt = instance->cnt;
// Build the upload
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint8_t key_data[sizeof(uint64_t)] = {0};
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
}
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t cnt_to_write = instance->cnt;
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t btn_to_write = instance->btn;
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t decrypted_to_write = instance->decrypted;
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t encrypted_to_write = instance->encrypted;
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
}
if(!flipper_format_rewind(flipper_format)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
uint32_t crc_to_write = instance->crc;
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
}
instance->encoder.is_running = true;
instance->encoder.front = 0;
FURI_LOG_I(
TAG,
"Encoder initialized: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X, Version=%s",
(unsigned long)instance->serial,
instance->btn,
instance->cnt,
instance->version == 0 ? "V4" : "V3");
ret = SubGhzProtocolStatusOk;
} while(false);
@@ -588,12 +592,22 @@ LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
if(!instance || !instance->encoder.upload || instance->encoder.repeat == 0 ||
!instance->encoder.is_running) {
if(instance) {
FURI_LOG_D(
TAG,
"Encoder yield stopped: repeat=%u, is_running=%d",
instance->encoder.repeat,
instance->encoder.is_running);
instance->encoder.is_running = false;
}
return level_duration_reset();
}
if(instance->encoder.front >= instance->encoder.size_upload) {
FURI_LOG_E(
TAG,
"Encoder front out of bounds: %zu >= %zu",
instance->encoder.front,
instance->encoder.size_upload);
instance->encoder.is_running = false;
instance->encoder.front = 0;
return level_duration_reset();
@@ -601,48 +615,101 @@ LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(instance->encoder.front < 5) {
FURI_LOG_D(
TAG,
"Encoder yield[%zu]: repeat=%u, level=%d, duration=%lu",
instance->encoder.front,
instance->encoder.repeat,
level_duration_get_level(ret),
level_duration_get_duration(ret));
}
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
FURI_LOG_I(
TAG, "Encoder completed one cycle, remaining repeat=%u", instance->encoder.repeat);
}
return ret;
}
void subghz_protocol_encoder_kia_v3_v4_set_button(void* context, uint8_t button) {
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->btn = button & 0x0F;
instance->generic.btn = instance->btn;
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
FURI_LOG_I(TAG, "Button set to 0x%X", instance->btn);
}
void subghz_protocol_encoder_kia_v3_v4_set_counter(void* context, uint16_t counter) {
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->cnt = counter;
instance->generic.cnt = instance->cnt;
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
FURI_LOG_I(TAG, "Counter set to 0x%04X", instance->cnt);
}
void subghz_protocol_encoder_kia_v3_v4_increment_counter(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
instance->cnt++;
instance->generic.cnt = instance->cnt;
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
FURI_LOG_I(TAG, "Counter incremented to 0x%04X", instance->cnt);
}
uint16_t subghz_protocol_encoder_kia_v3_v4_get_counter(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
return instance->cnt;
}
uint8_t subghz_protocol_encoder_kia_v3_v4_get_button(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV3V4* instance = context;
return instance->btn;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV3V4));
instance->base.protocol = &subghz_protocol_kia_v3_v4;
instance->generic.protocol_name = instance->base.protocol->name;
instance->version = 0;
instance->is_v3_sync = false;
return instance;
}
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
free(instance);
}
void subghz_protocol_decoder_kia_v3_v4_reset(void* context) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
instance->header_count = 0;
instance->raw_bit_count = 0;
instance->is_v3_sync = false;
instance->crc = 0;
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
}
void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
switch(instance->decoder.parser_step) {
case KiaV3V4DecoderStepReset:
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
case KiaV3V4DecoderStepReset:
if(level && (DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
kia_protocol_v3_v4_const.te_delta)) {
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
instance->decoder.te_last = duration;
instance->header_count = 1;
@@ -651,10 +718,9 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
case KiaV3V4DecoderStepCheckPreamble:
if(level) {
if(DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
if(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
kia_protocol_v3_v4_const.te_delta) {
instance->decoder.te_last = duration;
instance->header_count++;
} else if(duration > 1000 && duration < 1500) {
if(instance->header_count >= 8) {
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
@@ -678,10 +744,10 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
}
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta &&
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
kia_protocol_v3_v4_const.te_delta) &&
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v3_v4_const.te_short) <
kia_protocol_v3_v4_const.te_delta)) {
instance->header_count++;
} else if(duration > 1500) {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
@@ -698,12 +764,12 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
}
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
subghz_protocol_kia_v3_v4_const.te_delta) {
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
kia_protocol_v3_v4_const.te_delta) {
kia_v3_v4_add_raw_bit(instance, false);
} else if(
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_long) <
subghz_protocol_kia_v3_v4_const.te_delta) {
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_long) <
kia_protocol_v3_v4_const.te_delta) {
kia_v3_v4_add_raw_bit(instance, true);
} else {
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
@@ -728,7 +794,7 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
}
uint8_t subghz_protocol_decoder_kia_v3_v4_get_hash_data(void* context) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
@@ -738,99 +804,107 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
if(ret == SubGhzProtocolStatusOk) {
do {
// Write frequency
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) {
break;
}
// Write preset
if(!flipper_format_write_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(preset->name))) {
break;
}
// Write version-specific protocol name instead of generic "Kia V3/V4"
const char* version_name = (instance->version == 0) ? "Kia V4" : "Kia V3";
if(!flipper_format_write_string_cstr(flipper_format, "Protocol", version_name)) {
break;
}
// Write bit count
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) {
break;
}
// Write key
char key_str[20];
snprintf(key_str, sizeof(key_str), "%016llX", (unsigned long long)instance->generic.data);
if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) {
break;
}
// Write all fields needed by encoder
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
break;
}
uint32_t temp = instance->generic.btn;
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
break;
}
temp = instance->generic.cnt;
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
break;
}
// Write protocol-specific fields
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp = instance->version;
if(!flipper_format_write_uint32(flipper_format, "Version", &temp, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
temp = instance->version;
if(!flipper_format_write_uint32(flipper_format, "KIAVersion", &temp, 1)) {
break;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp = instance->crc;
temp = instance->crc;
if(!flipper_format_write_uint32(flipper_format, "CRC", &temp, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
break;
}
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize_check_count_bit(&instance->generic, flipper_format, 64);
if(ret == SubGhzProtocolStatusOk) {
if(instance->generic.data_count_bit < 64) {
ret = SubGhzProtocolStatusErrorParserBitCount;
}
}
if(ret == SubGhzProtocolStatusOk) {
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
instance->encrypted = 0;
}
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
instance->decrypted = 0;
}
uint32_t temp_version = 0;
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
instance->version = temp_version;
} else {
instance->version = 0;
}
uint32_t temp_crc = 0;
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
instance->crc = temp_crc;
} else {
instance->crc = 0;
}
if(instance->decrypted != 0) {
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
instance->generic.cnt = instance->decrypted & 0xFFFF;
}
if(instance->generic.data != 0) {
uint8_t b[8];
for(int i = 0; i < 8; i++) {
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
}
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
((uint32_t)reverse8(b[6]) << 16) |
((uint32_t)reverse8(b[5]) << 8) |
(uint32_t)reverse8(b[4]);
}
uint32_t temp = 0;
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(instance->generic.btn);
if(flipper_format_read_uint32(flipper_format, "Encrypted", &temp, 1)) {
instance->encrypted = temp;
}
if(flipper_format_read_uint32(flipper_format, "Decrypted", &temp, 1)) {
instance->decrypted = temp;
}
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &temp, 1)) {
instance->version = (uint8_t)temp;
}
if(flipper_format_read_uint32(flipper_format, "CRC", &temp, 1)) {
instance->crc = (uint8_t)temp;
}
subghz_custom_btn_set_max(5);
}
return ret;
}
@@ -842,17 +916,8 @@ static uint64_t compute_yek(uint64_t key) {
return yek;
}
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
uint8_t bytes[8];
for(int i = 0; i < 8; i++) {
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
}
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
return (calculated_crc == received_crc);
}
void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* output) {
furi_assert(context);
furi_check(context);
SubGhzProtocolDecoderKiaV3V4* instance = context;
uint64_t yek = compute_yek(instance->generic.data);
@@ -861,27 +926,42 @@ void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* out
uint32_t yek_hi = (uint32_t)(yek >> 32);
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Sn:%07lX Btn:%X [%s]\r\n"
"Dec:%08lX Cnt:%04lX\r\n"
"CRC:%X %s",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
instance->decrypted,
instance->generic.cnt,
instance->crc,
crc_valid ? "(OK)" : "(FAIL)");
if(instance->version == 0) {
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Serial:%07lX Btn:%01X CRC:%01X\r\n"
"Decr:%08lX Cnt:%04lX\r\n",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
instance->crc,
instance->decrypted,
instance->generic.cnt);
} else {
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Yek:%08lX%08lX\r\n"
"Serial:%07lX Btn:%01X\r\n"
"Decr:%08lX Cnt:%04lX\r\n",
kia_version_names[instance->version],
instance->generic.data_count_bit,
key_hi,
key_lo,
yek_hi,
yek_lo,
instance->generic.serial,
instance->generic.btn,
instance->decrypted,
instance->generic.cnt);
}
}
+826
View File
@@ -0,0 +1,826 @@
#include "kia_v6.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#include <furi_hal_crypto.h>
#define TAG "SubGhzProtocolKiaV6"
#define KIA_V6_XOR_MASK_LOW 0x84AF25FB
#define KIA_V6_XOR_MASK_HIGH 0x638766AB
#define KIA_V6_PREAMBLE_PAIRS_1 640
#define KIA_V6_PREAMBLE_PAIRS_2 38
#define KIA_V6_UPLOAD_SIZE 2000
static const SubGhzBlockConst subghz_protocol_kia_v6_const = {
.te_short = 200,
.te_long = 400,
.te_delta = 100,
.min_count_bit_for_found = 144,
};
static const uint8_t aes_sbox[256] = {
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
};
struct SubGhzProtocolDecoderKiaV6 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
ManchesterState manchester_state;
uint32_t data_part1_low;
uint32_t data_part1_high;
uint32_t stored_part1_low;
uint32_t stored_part1_high;
uint32_t stored_part2_low;
uint32_t stored_part2_high;
uint16_t data_part3;
uint8_t bit_count;
uint32_t saved_part1_low;
uint32_t saved_part1_high;
uint32_t saved_part2_low;
uint32_t saved_part2_high;
uint16_t saved_part3;
uint32_t saved_serial;
uint8_t saved_btn;
uint32_t saved_cnt;
uint8_t saved_fx;
uint8_t saved_crc1;
uint8_t saved_crc2;
bool saved_crc_valid;
uint8_t fx_field;
uint8_t crc1_field;
uint8_t crc2_field;
};
struct SubGhzProtocolEncoderKiaV6 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t stored_part1_low;
uint32_t stored_part1_high;
uint32_t stored_part2_low;
uint32_t stored_part2_high;
uint16_t data_part3;
uint8_t fx_field;
};
typedef enum {
KiaV6DecoderStepReset = 0,
KiaV6DecoderStepWaitFirstHigh,
KiaV6DecoderStepCountPreamble,
KiaV6DecoderStepWaitLongHigh,
KiaV6DecoderStepData,
} KiaV6DecoderStep;
static uint8_t kia_v6_crc8(uint8_t *data, int len, uint8_t init, uint8_t polynomial) {
uint8_t crc = init;
uint8_t *pbVar3 = data;
while (pbVar3 != data + len) {
crc = crc ^ *pbVar3;
for (int j = 8; j > 0; j--) {
uint8_t bVar1 = (uint8_t)(crc << 1);
if ((crc & 0x80) != 0) {
bVar1 = bVar1 ^ polynomial;
}
crc = bVar1;
}
pbVar3++;
}
return crc;
}
static uint8_t kia_v6_btn_to_custom(uint8_t btn) {
switch(btn) {
case 0x01: return 1; // Lock
case 0x02: return 2; // Unlock
case 0x03: return 3; // Trunk
case 0x04: return 4; // Panic
default: return 2; // Unknown → Unlock
}
}
static uint8_t kia_v6_custom_to_btn(uint8_t custom) {
switch(custom) {
case 1: return 0x01; // Lock
case 2: return 0x02; // Unlock
case 3: return 0x03; // Trunk
case 4: return 0x04; // Panic
default: return 0x02;
}
}
static void get_kia_v6_aes_key(uint8_t* aes_key) {
uint64_t keystore_a = 0x37CE21F8C9F862A8ULL ^ 0x5448455049524154ULL;
uint32_t keystore_a_hi = (keystore_a >> 32) & 0xFFFFFFFF;
uint32_t keystore_a_lo = keystore_a & 0xFFFFFFFF;
uint32_t uVar15_a = keystore_a_lo ^ KIA_V6_XOR_MASK_LOW;
uint32_t uVar5_a = KIA_V6_XOR_MASK_HIGH ^ keystore_a_hi;
uint64_t val64_a = ((uint64_t)uVar5_a << 32) | uVar15_a;
for (int i = 0; i < 8; i++) {
aes_key[i] = (val64_a >> (56 - i * 8)) & 0xFF;
}
uint64_t keystore_b = 0x3FC629F0C1F06AA0ULL ^ 0x5448455049524154ULL;
uint32_t keystore_b_hi = (keystore_b >> 32) & 0xFFFFFFFF;
uint32_t keystore_b_lo = keystore_b & 0xFFFFFFFF;
uint32_t uVar15_b = keystore_b_lo ^ KIA_V6_XOR_MASK_LOW;
uint32_t uVar5_b = KIA_V6_XOR_MASK_HIGH ^ keystore_b_hi;
uint64_t val64_b = ((uint64_t)uVar5_b << 32) | uVar15_b;
for (int i = 0; i < 8; i++) {
aes_key[i + 8] = (val64_b >> (56 - i * 8)) & 0xFF;
}
}
static bool kia_v6_decrypt(SubGhzProtocolDecoderKiaV6* instance) {
uint8_t encrypted_data[16];
encrypted_data[0] = (instance->stored_part1_high >> 8) & 0xFF;
encrypted_data[1] = instance->stored_part1_high & 0xFF;
encrypted_data[2] = (instance->stored_part1_low >> 24) & 0xFF;
encrypted_data[3] = (instance->stored_part1_low >> 16) & 0xFF;
encrypted_data[4] = (instance->stored_part1_low >> 8) & 0xFF;
encrypted_data[5] = instance->stored_part1_low & 0xFF;
encrypted_data[6] = (instance->stored_part2_high >> 24) & 0xFF;
encrypted_data[7] = (instance->stored_part2_high >> 16) & 0xFF;
encrypted_data[8] = (instance->stored_part2_high >> 8) & 0xFF;
encrypted_data[9] = instance->stored_part2_high & 0xFF;
encrypted_data[10] = (instance->stored_part2_low >> 24) & 0xFF;
encrypted_data[11] = (instance->stored_part2_low >> 16) & 0xFF;
encrypted_data[12] = (instance->stored_part2_low >> 8) & 0xFF;
encrypted_data[13] = instance->stored_part2_low & 0xFF;
encrypted_data[14] = (instance->data_part3 >> 8) & 0xFF;
encrypted_data[15] = instance->data_part3 & 0xFF;
uint8_t fx_byte0 = (instance->stored_part1_high >> 24) & 0xFF;
uint8_t fx_byte1 = (instance->stored_part1_high >> 16) & 0xFF;
instance->fx_field = ((fx_byte0 & 0xF) << 4) | (fx_byte1 & 0xF);
uint8_t aes_key[16];
get_kia_v6_aes_key(aes_key);
uint8_t decrypted_buf[16];
furi_hal_crypto_aes128_ecb_decrypt(aes_key, encrypted_data, decrypted_buf);
memcpy(encrypted_data, decrypted_buf, 16);
uint8_t *decrypted = encrypted_data;
uint8_t calculated_crc = kia_v6_crc8(decrypted, 15, 0xFF, 0x07);
uint8_t stored_crc = decrypted[15];
instance->generic.serial = ((uint32_t)decrypted[4] << 16) |
((uint32_t)decrypted[5] << 8) |
decrypted[6];
instance->generic.btn = decrypted[7];
instance->generic.cnt = ((uint32_t)decrypted[8] << 24) |
((uint32_t)decrypted[9] << 16) |
((uint32_t)decrypted[10] << 8) |
decrypted[11];
instance->crc1_field = decrypted[12];
instance->crc2_field = decrypted[15];
bool crc_valid = (calculated_crc ^ stored_crc) < 2;
instance->saved_serial = instance->generic.serial;
instance->saved_btn = instance->generic.btn;
instance->saved_cnt = instance->generic.cnt;
instance->saved_fx = instance->fx_field;
instance->saved_crc1 = instance->crc1_field;
instance->saved_crc2 = instance->crc2_field;
instance->saved_crc_valid = crc_valid;
instance->saved_part1_low = instance->stored_part1_low;
instance->saved_part1_high = instance->stored_part1_high;
instance->saved_part2_low = instance->stored_part2_low;
instance->saved_part2_high = instance->stored_part2_high;
instance->saved_part3 = instance->data_part3;
return crc_valid;
}
static void kia_v6_encrypt_payload(
uint8_t fx_field,
uint32_t serial,
uint8_t btn,
uint32_t cnt,
uint32_t* out_part1_low,
uint32_t* out_part1_high,
uint32_t* out_part2_low,
uint32_t* out_part2_high,
uint16_t* out_part3) {
uint8_t plain[16];
memset(plain, 0, 16);
plain[0] = fx_field;
plain[4] = (serial >> 16) & 0xFF;
plain[5] = (serial >> 8) & 0xFF;
plain[6] = serial & 0xFF;
plain[7] = btn & 0x0F;
plain[8] = (cnt >> 24) & 0xFF;
plain[9] = (cnt >> 16) & 0xFF;
plain[10] = (cnt >> 8) & 0xFF;
plain[11] = cnt & 0xFF;
plain[12] = aes_sbox[cnt & 0xFF];
plain[15] = kia_v6_crc8(plain, 15, 0xFF, 0x07);
uint8_t aes_key[16];
get_kia_v6_aes_key(aes_key);
uint8_t encrypted[16];
furi_hal_crypto_aes128_ecb_encrypt(aes_key, plain, encrypted);
memcpy(plain, encrypted, 16);
uint8_t fx_hi = 0x20 | (fx_field >> 4);
uint8_t fx_lo = fx_field & 0x0F;
*out_part1_high = ((uint32_t)fx_hi << 24) | ((uint32_t)fx_lo << 16) |
((uint32_t)plain[0] << 8) | plain[1];
*out_part1_low = ((uint32_t)plain[2] << 24) | ((uint32_t)plain[3] << 16) |
((uint32_t)plain[4] << 8) | plain[5];
*out_part2_high = ((uint32_t)plain[6] << 24) | ((uint32_t)plain[7] << 16) |
((uint32_t)plain[8] << 8) | plain[9];
*out_part2_low = ((uint32_t)plain[10] << 24) | ((uint32_t)plain[11] << 16) |
((uint32_t)plain[12] << 8) | plain[13];
*out_part3 = ((uint16_t)plain[14] << 8) | plain[15];
}
void* subghz_protocol_decoder_kia_v6_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV6* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV6));
instance->base.protocol = &subghz_protocol_kia_v6;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_kia_v6_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
free(instance);
}
void subghz_protocol_decoder_kia_v6_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
instance->decoder.parser_step = KiaV6DecoderStepReset;
instance->header_count = 0;
instance->bit_count = 0;
instance->data_part1_low = 0;
instance->data_part1_high = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void subghz_protocol_decoder_kia_v6_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
switch(instance->decoder.parser_step) {
case KiaV6DecoderStepReset:
if(level == 0) return;
if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
instance->decoder.parser_step = KiaV6DecoderStepWaitFirstHigh;
instance->decoder.te_last = duration;
instance->header_count = 0;
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
return;
case KiaV6DecoderStepWaitFirstHigh:
if(level != 0) return;
uint32_t diff_short = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short);
uint32_t diff_long = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long);
uint32_t diff = (diff_long < diff_short) ? diff_long : diff_short;
if(diff_long < subghz_protocol_kia_v6_const.te_delta && diff_long < diff_short) {
if(instance->header_count >= 0x259) {
instance->header_count = 0;
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV6DecoderStepWaitLongHigh;
return;
}
}
if(diff >= subghz_protocol_kia_v6_const.te_delta) {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
instance->decoder.te_last = duration;
instance->header_count++;
return;
} else {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
case KiaV6DecoderStepWaitLongHigh:
if(level == 0) {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
uint32_t diff_long_check = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long);
uint32_t diff_short_check = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short);
if(diff_long_check >= subghz_protocol_kia_v6_const.te_delta) {
if(diff_short_check >= subghz_protocol_kia_v6_const.te_delta) {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
}
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v6_const.te_long) >= subghz_protocol_kia_v6_const.te_delta) {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->data_part1_low = (uint32_t)(instance->decoder.decode_data & 0xFFFFFFFF);
instance->data_part1_high = (uint32_t)((instance->decoder.decode_data >> 32) & 0xFFFFFFFF);
instance->bit_count = instance->decoder.decode_count_bit;
instance->decoder.parser_step = KiaV6DecoderStepData;
return;
case KiaV6DecoderStepData: {
ManchesterEvent event;
bool data_bit;
if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
event = (level & 0x7F) << 1;
} else if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long) < subghz_protocol_kia_v6_const.te_delta) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
uint32_t uVar4 = instance->data_part1_low;
uint32_t uVar5 = (uVar4 << 1) | (data_bit ? 1 : 0);
uint32_t carry = (uVar4 >> 31) & 1;
uVar4 = (instance->data_part1_high << 1) | carry;
instance->data_part1_low = uVar5;
instance->data_part1_high = uVar4;
instance->decoder.decode_data = ((uint64_t)uVar4 << 32) | uVar5;
instance->bit_count++;
if(instance->bit_count == 0x40) {
instance->stored_part1_low = ~uVar5;
instance->stored_part1_high = ~uVar4;
instance->data_part1_low = 0;
instance->data_part1_high = 0;
} else if(instance->bit_count == 0x80) {
instance->stored_part2_low = ~uVar5;
instance->stored_part2_high = ~uVar4;
instance->data_part1_low = 0;
instance->data_part1_high = 0;
}
}
instance->decoder.te_last = duration;
if(instance->bit_count != subghz_protocol_kia_v6_const.min_count_bit_for_found) {
return;
}
instance->generic.data_count_bit = subghz_protocol_kia_v6_const.min_count_bit_for_found;
instance->data_part3 = ~((uint16_t)instance->data_part1_low);
instance->generic.data = ((uint64_t)instance->stored_part1_high << 32) | instance->stored_part1_low;
kia_v6_decrypt(instance);
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->data_part1_low = 0;
instance->data_part1_high = 0;
instance->bit_count = 0;
instance->decoder.parser_step = KiaV6DecoderStepReset;
return;
}
default:
return;
}
}
uint8_t subghz_protocol_decoder_kia_v6_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
uint8_t hash = 0;
hash ^= (instance->stored_part1_low >> 24) & 0xFF;
hash ^= (instance->stored_part1_low >> 16) & 0xFF;
hash ^= (instance->stored_part1_low >> 8) & 0xFF;
hash ^= instance->stored_part1_low & 0xFF;
hash ^= (instance->stored_part1_high >> 24) & 0xFF;
hash ^= (instance->stored_part1_high >> 16) & 0xFF;
hash ^= (instance->stored_part1_high >> 8) & 0xFF;
hash ^= instance->stored_part1_high & 0xFF;
return hash;
}
SubGhzProtocolStatus subghz_protocol_decoder_kia_v6_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(ret == SubGhzProtocolStatusOk) {
uint32_t key2_low = instance->stored_part2_low;
if(!flipper_format_write_uint32(flipper_format, "Key_2", &key2_low, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t key2_high = instance->stored_part2_high;
if(!flipper_format_write_uint32(flipper_format, "Key_3", &key2_high, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t key3 = instance->data_part3;
if(!flipper_format_write_uint32(flipper_format, "Key_4", &key3, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t fx = instance->fx_field;
if(!flipper_format_write_uint32(flipper_format, "Fx", &fx, 1)) {
ret = SubGhzProtocolStatusErrorParserOthers;
}
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_kia_v6_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret == SubGhzProtocolStatusOk) {
if(instance->generic.data_count_bit < subghz_protocol_kia_v6_const.min_count_bit_for_found) {
ret = SubGhzProtocolStatusErrorParserBitCount;
}
}
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp;
if(flipper_format_read_uint32(flipper_format, "Key_2", &temp, 1)) {
instance->stored_part2_low = temp;
}
if(flipper_format_read_uint32(flipper_format, "Key_3", &temp, 1)) {
instance->stored_part2_high = temp;
}
if(flipper_format_read_uint32(flipper_format, "Key_4", &temp, 1)) {
instance->data_part3 = (uint16_t)temp;
}
if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) {
instance->fx_field = (uint8_t)temp;
}
instance->stored_part1_high = (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF);
instance->stored_part1_low = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
kia_v6_decrypt(instance);
}
return ret;
}
void subghz_protocol_decoder_kia_v6_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderKiaV6* instance = context;
uint32_t key1_hi = instance->saved_part1_high;
uint32_t key1_lo = instance->saved_part1_low;
uint32_t key2_hi = instance->saved_part2_high;
uint32_t key2_lo = instance->saved_part2_low;
uint32_t key2_uVar4 = key2_hi << 16;
uint32_t key2_uVar2 = key2_lo >> 16;
uint32_t key2_uVar1 = key2_hi >> 16;
uint32_t key2_combined = key2_uVar4 | key2_uVar2;
uint32_t key2_uVar3 = key2_lo << 16;
uint32_t key2_second = (instance->saved_part3 & 0xFFFF) | key2_uVar3;
uint32_t serial_6 = instance->saved_serial & 0xFFFFFF;
const char* crc_status = instance->saved_crc_valid ? "(OK)" : "(FAIL)";
const char* btn_name;
switch(instance->saved_btn & 0x0F) {
case 0x01: btn_name = "Lock"; break;
case 0x02: btn_name = "Unlock"; break;
case 0x03: btn_name = "Trunk"; break;
case 0x04: btn_name = "Panic"; break;
default: btn_name = "Unknown"; break;
}
furi_string_printf(
output,
"%s %dbit\r\n"
"%08lX%08lX%04lX\r\n"
"%08lX%08lX Fx:%02X\r\n"
"Ser:%06lX Btn:%01X[%s]\r\n"
"Cnt:%08lX CRC:%02X-%02X\r\n"
"CRC %s",
instance->generic.protocol_name,
instance->generic.data_count_bit,
key1_hi,
key1_lo,
key2_uVar1,
key2_combined,
key2_second,
instance->saved_fx,
serial_6,
instance->saved_btn & 0x0F,
btn_name,
instance->saved_cnt,
instance->saved_crc1,
instance->saved_crc2,
crc_status);
}
static inline void kia_v6_encode_manchester_bit(
LevelDuration* upload,
size_t* index,
bool bit,
uint32_t te) {
if(bit) {
upload[(*index)++] = level_duration_make(false, te);
upload[(*index)++] = level_duration_make(true, te);
} else {
upload[(*index)++] = level_duration_make(true, te);
upload[(*index)++] = level_duration_make(false, te);
}
}
static void kia_v6_encode_message(
LevelDuration* upload,
size_t* index,
int preamble_pairs,
uint32_t p1_lo,
uint32_t p1_hi,
uint32_t p2_lo,
uint32_t p2_hi,
uint16_t p3) {
const uint32_t te_short = subghz_protocol_kia_v6_const.te_short;
for(int i = 0; i < preamble_pairs; i++) {
upload[(*index)++] = level_duration_make(true, te_short);
upload[(*index)++] = level_duration_make(false, te_short);
}
upload[(*index)++] = level_duration_make(false, te_short);
upload[(*index)++] = level_duration_make(true, subghz_protocol_kia_v6_const.te_long);
upload[(*index)++] = level_duration_make(false, te_short);
for(int b = 60; b >= 0; b--) {
uint32_t word = (b >= 32) ? p1_hi : p1_lo;
int shift = (b >= 32) ? (b - 32) : b;
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
}
for(int b = 63; b >= 0; b--) {
uint32_t word = (b >= 32) ? p2_hi : p2_lo;
int shift = (b >= 32) ? (b - 32) : b;
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
}
for(int b = 15; b >= 0; b--) {
kia_v6_encode_manchester_bit(upload, index, ((~p3) >> b) & 1, te_short);
}
}
// FIX: build_upload usa direttamente instance->generic.btn, già risolto dal chiamante
static void kia_v6_encoder_build_upload(SubGhzProtocolEncoderKiaV6* instance) {
furi_assert(instance);
kia_v6_encrypt_payload(
instance->fx_field,
instance->generic.serial,
instance->generic.btn & 0x0F,
instance->generic.cnt,
&instance->stored_part1_low,
&instance->stored_part1_high,
&instance->stored_part2_low,
&instance->stored_part2_high,
&instance->data_part3);
uint32_t p1_lo = instance->stored_part1_low;
uint32_t p1_hi = instance->stored_part1_high;
uint32_t p2_lo = instance->stored_part2_low;
uint32_t p2_hi = instance->stored_part2_high;
uint16_t p3 = instance->data_part3;
size_t index = 0;
kia_v6_encode_message(instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_1, p1_lo, p1_hi, p2_lo, p2_hi, p3);
instance->encoder.upload[index++] = level_duration_make(false, subghz_protocol_kia_v6_const.te_long);
kia_v6_encode_message(instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_2, p1_lo, p1_hi, p2_lo, p2_hi, p3);
instance->encoder.upload[index++] = level_duration_make(false, subghz_protocol_kia_v6_const.te_long);
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
void* subghz_protocol_encoder_kia_v6_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
if(!instance) return NULL;
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
instance->base.protocol = &subghz_protocol_kia_v6;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.size_upload = KIA_V6_UPLOAD_SIZE;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
if(!instance->encoder.upload) {
free(instance);
return NULL;
}
instance->encoder.repeat = 3;
instance->encoder.front = 0;
instance->encoder.is_running = false;
subghz_custom_btn_set_max(4);
return instance;
}
void subghz_protocol_encoder_kia_v6_free(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKiaV6* instance = context;
if(instance->encoder.upload) {
free(instance->encoder.upload);
}
free(instance);
}
SubGhzProtocolStatus subghz_protocol_encoder_kia_v6_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolEncoderKiaV6* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 3;
do {
SubGhzProtocolDecoderKiaV6 dec = {0};
ret = subghz_block_generic_deserialize(&dec.generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
if(dec.generic.data_count_bit < subghz_protocol_kia_v6_const.min_count_bit_for_found) {
ret = SubGhzProtocolStatusErrorParserBitCount;
break;
}
uint32_t temp;
if(flipper_format_read_uint32(flipper_format, "Key_2", &temp, 1)) {
dec.stored_part2_low = temp;
}
if(flipper_format_read_uint32(flipper_format, "Key_3", &temp, 1)) {
dec.stored_part2_high = temp;
}
if(flipper_format_read_uint32(flipper_format, "Key_4", &temp, 1)) {
dec.data_part3 = (uint16_t)temp;
}
if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) {
dec.fx_field = (uint8_t)temp;
}
dec.stored_part1_high = (uint32_t)((dec.generic.data >> 32) & 0xFFFFFFFF);
dec.stored_part1_low = (uint32_t)(dec.generic.data & 0xFFFFFFFF);
kia_v6_decrypt(&dec);
instance->generic.serial = dec.generic.serial;
instance->generic.btn = dec.generic.btn;
instance->generic.cnt = dec.generic.cnt;
instance->generic.data_count_bit = subghz_protocol_kia_v6_const.min_count_bit_for_found;
instance->fx_field = dec.fx_field;
// Salva il btn originale come indice custom 1-4 (identico a Suzuki)
if(subghz_custom_btn_get_original() == 0) {
subghz_custom_btn_set_original(kia_v6_btn_to_custom(instance->generic.btn));
}
subghz_custom_btn_set_max(4);
// Incrementa cnt
if(instance->generic.cnt < 0xFFFFFFFF) {
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
}
} else {
instance->generic.cnt = 0;
}
// Risolvi btn dal tasto premuto (identico a Suzuki)
instance->generic.btn = kia_v6_custom_to_btn(
subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK
? subghz_custom_btn_get_original()
: subghz_custom_btn_get());
kia_v6_encoder_build_upload(instance);
// Aggiorna il file .sub con i nuovi valori cifrati
uint32_t file_part1_low, file_part1_high, file_part2_low, file_part2_high;
uint16_t file_part3;
kia_v6_encrypt_payload(
instance->fx_field,
instance->generic.serial,
instance->generic.btn & 0x0F,
instance->generic.cnt,
&file_part1_low,
&file_part1_high,
&file_part2_low,
&file_part2_high,
&file_part3);
uint64_t new_key = ((uint64_t)file_part1_high << 32) | file_part1_low;
uint8_t key_data[sizeof(uint64_t)];
for(size_t i = 0; i < sizeof(uint64_t); i++) {
key_data[sizeof(uint64_t) - i - 1] = (new_key >> (i * 8)) & 0xFF;
}
if(!flipper_format_rewind(flipper_format)) break;
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
ret = SubGhzProtocolStatusErrorParserKey;
break;
}
flipper_format_update_uint32(flipper_format, "Key_2", &file_part2_low, 1);
flipper_format_update_uint32(flipper_format, "Key_3", &file_part2_high, 1);
uint32_t file_key3 = file_part3;
flipper_format_update_uint32(flipper_format, "Key_4", &file_key3, 1);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_kia_v6_stop(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKiaV6* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_kia_v6_yield(void* context) {
furi_assert(context);
SubGhzProtocolEncoderKiaV6* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat <= 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
instance->encoder.front++;
if(instance->encoder.front >= instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
const SubGhzProtocolDecoder subghz_protocol_kia_v6_decoder = {
.alloc = subghz_protocol_decoder_kia_v6_alloc,
.free = subghz_protocol_decoder_kia_v6_free,
.feed = subghz_protocol_decoder_kia_v6_feed,
.reset = subghz_protocol_decoder_kia_v6_reset,
.get_hash_data = subghz_protocol_decoder_kia_v6_get_hash_data,
.serialize = subghz_protocol_decoder_kia_v6_serialize,
.deserialize = subghz_protocol_decoder_kia_v6_deserialize,
.get_string = subghz_protocol_decoder_kia_v6_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_kia_v6_encoder = {
.alloc = subghz_protocol_encoder_kia_v6_alloc,
.free = subghz_protocol_encoder_kia_v6_free,
.deserialize = subghz_protocol_encoder_kia_v6_deserialize,
.stop = subghz_protocol_encoder_kia_v6_stop,
.yield = subghz_protocol_encoder_kia_v6_yield,
};
const SubGhzProtocol subghz_protocol_kia_v6 = {
.name = SUBGHZ_PROTOCOL_KIA_V6_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v6_decoder,
.encoder = &subghz_protocol_kia_v6_encoder,
};
File diff suppressed because it is too large Load Diff
+711
View File
@@ -0,0 +1,711 @@
#include "kia_v7.h"
#include <string.h>
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
#define KIA_V7_PREAMBLE_PAIRS 0x13F
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
#define KIA_V7_HEADER 0x4C
#define KIA_V7_TAIL_GAP_US 0x7D0
#define KIA_V7_KEY_BITS 64U
#define KIA_V7_DEFAULT_TX_REPEAT 10U
static const SubGhzBlockConst subghz_protocol_kia_v7_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = KIA_V7_KEY_BITS,
};
typedef enum {
KiaV7DecoderStepReset = 0,
KiaV7DecoderStepPreamble = 1,
KiaV7DecoderStepSyncLow = 2,
KiaV7DecoderStepData = 3,
} KiaV7DecoderStep;
struct SubGhzProtocolDecoderKiaV7 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint16_t preamble_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
struct SubGhzProtocolEncoderKiaV7 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint8_t tx_bit_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x4CU;
for(size_t index = 0; index < len; index++) {
crc ^= data[index];
for(uint8_t bit = 0; bit < 8; bit++) {
const bool msb = (crc & 0x80U) != 0U;
crc <<= 1U;
if(msb) {
crc ^= 0x7FU;
}
}
}
return crc;
}
static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
for(size_t index = 0; index < 8; index++) {
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
}
}
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t data = 0;
for(size_t index = 0; index < 8; index++) {
data = (data << 8U) | bytes[index];
}
return data;
}
static bool kia_v7_is_short(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_short) <
subghz_protocol_kia_v7_const.te_delta;
}
static bool kia_v7_is_long(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_long) <
subghz_protocol_kia_v7_const.te_delta;
}
static const char* kia_v7_get_button_name(uint8_t button) {
switch(button) {
case 0x01:
return "LOCK";
case 0x02:
return "UNLOCK";
case 0x03:
case 0x08:
return "BOOT";
default:
return "??";
}
}
static SubGhzProtocolStatus
kia_v7_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, kia_v7_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;
}
static void kia_v7_decode_key_common(
SubGhzBlockGeneric* generic,
uint8_t* decoded_button,
uint8_t* fixed_high_byte,
uint8_t* crc_calculated,
uint8_t* crc_raw,
bool* crc_valid) {
uint8_t bytes[8];
kia_v7_u64_to_bytes_be(generic->data, bytes);
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
const uint8_t button = bytes[6] & 0x0FU;
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
const uint8_t crc_pkt = bytes[7];
generic->serial = serial & 0x0FFFFFFFU;
generic->btn = button;
generic->cnt = counter;
generic->data_count_bit = KIA_V7_KEY_BITS;
if(decoded_button) {
*decoded_button = button;
}
if(fixed_high_byte) {
*fixed_high_byte = bytes[0];
}
if(crc_calculated) {
*crc_calculated = crc_calc;
}
if(crc_raw) {
*crc_raw = crc_pkt;
}
if(crc_valid) {
*crc_valid = (crc_calc == crc_pkt);
}
}
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static uint64_t kia_v7_encode_key(
uint8_t fixed_high_byte,
uint32_t serial,
uint8_t button,
uint16_t counter,
uint8_t* crc_out) {
uint8_t bytes[8];
serial &= 0x0FFFFFFFU;
button &= 0x0FU;
bytes[0] = fixed_high_byte;
bytes[1] = (counter >> 8U) & 0xFFU;
bytes[2] = counter & 0xFFU;
bytes[3] = (serial >> 20U) & 0xFFU;
bytes[4] = (serial >> 12U) & 0xFFU;
bytes[5] = (serial >> 4U) & 0xFFU;
bytes[6] = ((serial & 0x0FU) << 4U) | button;
bytes[7] = kia_v7_crc8(bytes, 7);
if(crc_out) {
*crc_out = bytes[7];
}
return kia_v7_bytes_to_u64_be(bytes);
}
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
furi_check(instance);
const LevelDuration high_short =
level_duration_make(true, subghz_protocol_kia_v7_const.te_short);
const LevelDuration low_short =
level_duration_make(false, subghz_protocol_kia_v7_const.te_short);
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
instance->tx_bit_count :
64U;
size_t final_size = 0;
for(uint8_t pass = 0; pass < 2; pass++) {
size_t index = pass;
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_short;
}
if((index + 1U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
if((index + 2U) > max_size) {
return false;
}
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
instance->encoder.upload[index++] = value ? high_short : low_short;
instance->encoder.upload[index++] = value ? low_short : high_short;
}
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_tail;
final_size = index;
}
instance->encoder.front = 0;
instance->encoder.size_upload = final_size;
return true;
}
const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder = {
.alloc = kia_protocol_decoder_v7_alloc,
.free = kia_protocol_decoder_v7_free,
.feed = kia_protocol_decoder_v7_feed,
.reset = kia_protocol_decoder_v7_reset,
.get_hash_data = kia_protocol_decoder_v7_get_hash_data,
.serialize = kia_protocol_decoder_v7_serialize,
.deserialize = kia_protocol_decoder_v7_deserialize,
.get_string = kia_protocol_decoder_v7_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder = {
.alloc = kia_protocol_encoder_v7_alloc,
.free = kia_protocol_encoder_v7_free,
.deserialize = kia_protocol_encoder_v7_deserialize,
.stop = kia_protocol_encoder_v7_stop,
.yield = kia_protocol_encoder_v7_yield,
};
const SubGhzProtocol subghz_protocol_kia_v7 = {
.name = KIA_PROTOCOL_V7_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_kia_v7_decoder,
.encoder = &subghz_protocol_kia_v7_encoder,
};
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
furi_check(instance);
instance->base.protocol = &subghz_protocol_kia_v7;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 1;
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
instance->encoder.upload = malloc(KIA_V7_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void kia_protocol_encoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
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, KIA_V7_KEY_BITS);
if(load_st != SubGhzProtocolStatusOk) {
break;
}
instance->tx_bit_count =
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
(uint8_t)instance->generic.data_count_bit :
64U;
kia_v7_decode_key_encoder(instance);
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 = (uint16_t)u32;
}
instance->generic.btn &= 0x0FU;
instance->generic.cnt &= 0xFFFFU;
instance->generic.serial &= 0x0FFFFFFFU;
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn,
(uint16_t)instance->generic.cnt,
&instance->crc_calculated);
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Repeat", &u32, 1)) {
u32 = KIA_V7_DEFAULT_TX_REPEAT;
}
instance->encoder.repeat = u32;
if(!kia_v7_encoder_get_upload(instance)) {
break;
}
if(instance->encoder.size_upload == 0) {
break;
}
flipper_format_rewind(flipper_format);
uint8_t key_data[sizeof(uint64_t)];
kia_v7_u64_to_bytes_be(instance->generic.data, key_data);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
break;
}
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void kia_protocol_encoder_v7_stop(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
instance->encoder.is_running = false;
}
LevelDuration kia_protocol_encoder_v7_yield(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return duration;
}
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
furi_check(instance);
instance->base.protocol = &subghz_protocol_kia_v7;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void kia_protocol_decoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
free(instance);
}
void kia_protocol_decoder_v7_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
instance->decoder.parser_step = KiaV7DecoderStepReset;
instance->decoder.te_last = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
ManchesterEvent event = ManchesterEventReset;
bool data = false;
switch(instance->decoder.parser_step) {
case KiaV7DecoderStepReset:
if(level && kia_v7_is_short(duration)) {
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
instance->decoder.te_last = duration;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case KiaV7DecoderStepPreamble:
if(level) {
if(kia_v7_is_long(duration) && kia_v7_is_short(instance->decoder.te_last)) {
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else if(kia_v7_is_short(duration)) {
instance->decoder.te_last = duration;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else {
if(kia_v7_is_short(duration) && kia_v7_is_short(instance->decoder.te_last)) {
instance->preamble_count++;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
}
break;
case KiaV7DecoderStepSyncLow:
if(!level && kia_v7_is_short(duration) && kia_v7_is_long(instance->decoder.te_last)) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepData;
}
break;
case KiaV7DecoderStepData: {
if(kia_v7_is_short(duration)) {
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
} else if(kia_v7_is_long(duration)) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
event = ManchesterEventReset;
}
if(kia_v7_is_short(duration) || kia_v7_is_long(duration)) {
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 == KIA_V7_KEY_BITS) {
const uint64_t candidate = ~instance->decoder.decode_data;
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
if(hdr == KIA_V7_HEADER) {
instance->generic.data = candidate;
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
kia_v7_decode_key_decoder(instance);
if(instance->crc_valid) {
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
} else {
instance->generic.data = 0;
instance->generic.data_count_bit = 0;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
} else {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
}
break;
}
}
}
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit >> 3U) + 1U);
}
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%01X [%s] CRC:%02X [%s]",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->generic.data,
instance->generic.serial & 0x0FFFFFFFU,
instance->generic.cnt & 0xFFFFU,
instance->decoded_button & 0x0FU,
kia_v7_get_button_name(instance->decoded_button),
instance->crc_calculated,
instance->crc_valid ? "OK" : "ERR");
}
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
uint32_t serial = instance->generic.serial & 0x0FFFFFFFU;
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t btn_u32 = (uint32_t)(instance->decoded_button & 0x0FU);
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t cnt_u32 = (uint32_t)(instance->generic.cnt & 0xFFFFU);
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
if(!flipper_format_write_uint32(flipper_format, "Repeat", &repeat_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return kia_v7_write_display(
flipper_format, instance->generic.protocol_name, instance->decoded_button);
}
SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(!flipper_format_rewind(flipper_format)) {
return SubGhzProtocolStatusErrorParserOthers;
}
kia_v7_decode_key_decoder(instance);
uint32_t ser_u32 = 0;
uint32_t btn_u32 = 0;
uint32_t cnt_u32 = 0;
bool got_serial = false;
bool got_btn = false;
bool got_cnt = false;
flipper_format_rewind(flipper_format);
got_serial = flipper_format_read_uint32(flipper_format, "Serial", &ser_u32, 1);
flipper_format_rewind(flipper_format);
got_btn = flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
flipper_format_rewind(flipper_format);
got_cnt = flipper_format_read_uint32(flipper_format, "Cnt", &cnt_u32, 1);
if(got_serial || got_btn || got_cnt) {
if(got_serial) {
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
}
if(got_btn) {
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
}
if(got_cnt) {
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
}
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn & 0x0FU,
(uint16_t)(instance->generic.cnt & 0xFFFFU),
&instance->crc_calculated);
kia_v7_decode_key_decoder(instance);
}
return SubGhzProtocolStatusOk;
}
+708
View File
@@ -0,0 +1,708 @@
#include "kia_v7.h"
#include <string.h>
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
#define KIA_V7_PREAMBLE_PAIRS 0x13F
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
#define KIA_V7_HEADER 0x4C
#define KIA_V7_TAIL_GAP_US 0x7D0
#define KIA_V7_KEY_BITS 64U
#define KIA_V7_DEFAULT_TX_REPEAT 10U
static const SubGhzBlockConst kia_protocol_v7_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = KIA_V7_KEY_BITS,
};
typedef enum {
KiaV7DecoderStepReset = 0,
KiaV7DecoderStepPreamble = 1,
KiaV7DecoderStepSyncLow = 2,
KiaV7DecoderStepData = 3,
} KiaV7DecoderStep;
struct SubGhzProtocolDecoderKiaV7 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint16_t preamble_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
struct SubGhzProtocolEncoderKiaV7 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint8_t tx_bit_count;
uint8_t decoded_button;
uint8_t fixed_high_byte;
uint8_t crc_calculated;
uint8_t crc_raw;
bool crc_valid;
};
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
uint8_t crc = 0x4CU;
for(size_t index = 0; index < len; index++) {
crc ^= data[index];
for(uint8_t bit = 0; bit < 8; bit++) {
const bool msb = (crc & 0x80U) != 0U;
crc <<= 1U;
if(msb) {
crc ^= 0x7FU;
}
}
}
return crc;
}
static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
for(size_t index = 0; index < 8; index++) {
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
}
}
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t data = 0;
for(size_t index = 0; index < 8; index++) {
data = (data << 8U) | bytes[index];
}
return data;
}
static bool kia_v7_is_short(uint32_t duration) {
return DURATION_DIFF(duration, kia_protocol_v7_const.te_short) <
kia_protocol_v7_const.te_delta;
}
static bool kia_v7_is_long(uint32_t duration) {
return DURATION_DIFF(duration, kia_protocol_v7_const.te_long) < kia_protocol_v7_const.te_delta;
}
static const char* kia_v7_get_button_name(uint8_t button) {
switch(button) {
case 0x01:
return "Lock";
case 0x02:
return "Unlock";
case 0x03:
case 0x08:
return "Trunk";
default:
return "??";
}
}
static SubGhzProtocolStatus
kia_v7_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, kia_v7_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;
}
static void kia_v7_decode_key_common(
SubGhzBlockGeneric* generic,
uint8_t* decoded_button,
uint8_t* fixed_high_byte,
uint8_t* crc_calculated,
uint8_t* crc_raw,
bool* crc_valid) {
uint8_t bytes[8];
kia_v7_u64_to_bytes_be(generic->data, bytes);
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
const uint8_t button = bytes[6] & 0x0FU;
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
const uint8_t crc_pkt = bytes[7];
generic->serial = serial & 0x0FFFFFFFU;
generic->btn = button;
generic->cnt = counter;
generic->data_count_bit = KIA_V7_KEY_BITS;
if(decoded_button) {
*decoded_button = button;
}
if(fixed_high_byte) {
*fixed_high_byte = bytes[0];
}
if(crc_calculated) {
*crc_calculated = crc_calc;
}
if(crc_raw) {
*crc_raw = crc_pkt;
}
if(crc_valid) {
*crc_valid = (crc_calc == crc_pkt);
}
}
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static uint64_t kia_v7_encode_key(
uint8_t fixed_high_byte,
uint32_t serial,
uint8_t button,
uint16_t counter,
uint8_t* crc_out) {
uint8_t bytes[8];
serial &= 0x0FFFFFFFU;
button &= 0x0FU;
bytes[0] = fixed_high_byte;
bytes[1] = (counter >> 8U) & 0xFFU;
bytes[2] = counter & 0xFFU;
bytes[3] = (serial >> 20U) & 0xFFU;
bytes[4] = (serial >> 12U) & 0xFFU;
bytes[5] = (serial >> 4U) & 0xFFU;
bytes[6] = ((serial & 0x0FU) << 4U) | button;
bytes[7] = kia_v7_crc8(bytes, 7);
if(crc_out) {
*crc_out = bytes[7];
}
return kia_v7_bytes_to_u64_be(bytes);
}
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
kia_v7_decode_key_common(
&instance->generic,
&instance->decoded_button,
&instance->fixed_high_byte,
&instance->crc_calculated,
&instance->crc_raw,
&instance->crc_valid);
}
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
furi_check(instance);
const LevelDuration high_short = level_duration_make(true, kia_protocol_v7_const.te_short);
const LevelDuration low_short = level_duration_make(false, kia_protocol_v7_const.te_short);
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
instance->tx_bit_count :
64U;
size_t final_size = 0;
for(uint8_t pass = 0; pass < 2; pass++) {
size_t index = pass;
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_short;
}
if((index + 1U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
if((index + 2U) > max_size) {
return false;
}
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
instance->encoder.upload[index++] = value ? high_short : low_short;
instance->encoder.upload[index++] = value ? low_short : high_short;
}
if((index + 2U) > max_size) {
return false;
}
instance->encoder.upload[index++] = high_short;
instance->encoder.upload[index++] = low_tail;
final_size = index;
}
instance->encoder.front = 0;
instance->encoder.size_upload = final_size;
return true;
}
const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
.alloc = kia_protocol_decoder_v7_alloc,
.free = kia_protocol_decoder_v7_free,
.feed = kia_protocol_decoder_v7_feed,
.reset = kia_protocol_decoder_v7_reset,
.get_hash_data = kia_protocol_decoder_v7_get_hash_data,
.serialize = kia_protocol_decoder_v7_serialize,
.deserialize = kia_protocol_decoder_v7_deserialize,
.get_string = kia_protocol_decoder_v7_get_string,
};
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
.alloc = kia_protocol_encoder_v7_alloc,
.free = kia_protocol_encoder_v7_free,
.deserialize = kia_protocol_encoder_v7_deserialize,
.stop = kia_protocol_encoder_v7_stop,
.yield = kia_protocol_encoder_v7_yield,
};
const SubGhzProtocol kia_protocol_v7 = {
.name = KIA_PROTOCOL_V7_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &kia_protocol_v7_decoder,
.encoder = &kia_protocol_v7_encoder,
};
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
furi_check(instance);
instance->base.protocol = &kia_protocol_v7;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 1;
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
instance->encoder.upload = malloc(KIA_V7_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void kia_protocol_encoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderKiaV7* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
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, KIA_V7_KEY_BITS);
if(load_st != SubGhzProtocolStatusOk) {
break;
}
instance->tx_bit_count =
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
(uint8_t)instance->generic.data_count_bit :
64U;
kia_v7_decode_key_encoder(instance);
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 = (uint16_t)u32;
}
instance->generic.btn &= 0x0FU;
instance->generic.cnt &= 0xFFFFU;
instance->generic.serial &= 0x0FFFFFFFU;
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn,
(uint16_t)instance->generic.cnt,
&instance->crc_calculated);
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(flipper_format, "Repeat", &u32, 1)) {
u32 = KIA_V7_DEFAULT_TX_REPEAT;
}
instance->encoder.repeat = u32;
if(!kia_v7_encoder_get_upload(instance)) {
break;
}
if(instance->encoder.size_upload == 0) {
break;
}
flipper_format_rewind(flipper_format);
uint8_t key_data[sizeof(uint64_t)];
kia_v7_u64_to_bytes_be(instance->generic.data, key_data);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
break;
}
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void kia_protocol_encoder_v7_stop(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
instance->encoder.is_running = false;
}
LevelDuration kia_protocol_encoder_v7_yield(void* context) {
SubGhzProtocolEncoderKiaV7* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return duration;
}
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
furi_check(instance);
instance->base.protocol = &kia_protocol_v7;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void kia_protocol_decoder_v7_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
free(instance);
}
void kia_protocol_decoder_v7_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
instance->decoder.parser_step = KiaV7DecoderStepReset;
instance->decoder.te_last = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
}
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
ManchesterEvent event = ManchesterEventReset;
bool data = false;
switch(instance->decoder.parser_step) {
case KiaV7DecoderStepReset:
if(level && kia_v7_is_short(duration)) {
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
instance->decoder.te_last = duration;
instance->preamble_count = 0;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
break;
case KiaV7DecoderStepPreamble:
if(level) {
if(kia_v7_is_long(duration) && kia_v7_is_short(instance->decoder.te_last)) {
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else if(kia_v7_is_short(duration)) {
instance->decoder.te_last = duration;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
} else {
if(kia_v7_is_short(duration) && kia_v7_is_short(instance->decoder.te_last)) {
instance->preamble_count++;
} else {
instance->decoder.parser_step = KiaV7DecoderStepReset;
}
}
break;
case KiaV7DecoderStepSyncLow:
if(!level && kia_v7_is_short(duration) && kia_v7_is_long(instance->decoder.te_last)) {
instance->decoder.te_last = duration;
instance->decoder.parser_step = KiaV7DecoderStepData;
}
break;
case KiaV7DecoderStepData: {
if(kia_v7_is_short(duration)) {
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
} else if(kia_v7_is_long(duration)) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
event = ManchesterEventReset;
}
if(kia_v7_is_short(duration) || kia_v7_is_long(duration)) {
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 == KIA_V7_KEY_BITS) {
const uint64_t candidate = ~instance->decoder.decode_data;
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
if(hdr == KIA_V7_HEADER) {
instance->generic.data = candidate;
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
kia_v7_decode_key_decoder(instance);
if(instance->crc_valid) {
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
} else {
instance->generic.data = 0;
instance->generic.data_count_bit = 0;
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
} else {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = KiaV7DecoderStepReset;
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
}
}
break;
}
}
}
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit >> 3U) + 1U);
}
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Cnt:%04lX\r\n"
"Btn:%01X [%s] CRC:%02X [%s]",
instance->generic.protocol_name,
instance->generic.data_count_bit,
instance->generic.data,
instance->generic.serial & 0x0FFFFFFFU,
instance->generic.cnt & 0xFFFFU,
instance->decoded_button & 0x0FU,
kia_v7_get_button_name(instance->decoded_button),
instance->crc_calculated,
instance->crc_valid ? "OK" : "ERR");
}
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
kia_v7_decode_key_decoder(instance);
SubGhzProtocolStatus status =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
if(status != SubGhzProtocolStatusOk) {
return status;
}
uint32_t serial = instance->generic.serial & 0x0FFFFFFFU;
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t btn_u32 = (uint32_t)(instance->decoded_button & 0x0FU);
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t cnt_u32 = (uint32_t)(instance->generic.cnt & 0xFFFFU);
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
if(!flipper_format_write_uint32(flipper_format, "Repeat", &repeat_u32, 1)) {
return SubGhzProtocolStatusErrorParserOthers;
}
return kia_v7_write_display(
flipper_format, instance->generic.protocol_name, instance->decoded_button);
}
SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderKiaV7* instance = context;
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
if(status != SubGhzProtocolStatusOk) {
return status;
}
if(!flipper_format_rewind(flipper_format)) {
return SubGhzProtocolStatusErrorParserOthers;
}
kia_v7_decode_key_decoder(instance);
uint32_t ser_u32 = 0;
uint32_t btn_u32 = 0;
uint32_t cnt_u32 = 0;
bool got_serial = false;
bool got_btn = false;
bool got_cnt = false;
flipper_format_rewind(flipper_format);
got_serial = flipper_format_read_uint32(flipper_format, "Serial", &ser_u32, 1);
flipper_format_rewind(flipper_format);
got_btn = flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
flipper_format_rewind(flipper_format);
got_cnt = flipper_format_read_uint32(flipper_format, "Cnt", &cnt_u32, 1);
if(got_serial || got_btn || got_cnt) {
if(got_serial) {
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
}
if(got_btn) {
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
}
if(got_cnt) {
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
}
instance->generic.data = kia_v7_encode_key(
instance->fixed_high_byte,
instance->generic.serial,
instance->generic.btn & 0x0FU,
(uint16_t)(instance->generic.cnt & 0xFFFFU),
&instance->crc_calculated);
kia_v7_decode_key_decoder(instance);
}
return SubGhzProtocolStatusOk;
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#include <lib/toolbox/manchester_decoder.h>
#define KIA_PROTOCOL_V7_NAME "Kia V7"
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
extern const SubGhzProtocolDecoder kia_protocol_v7_decoder;
extern const SubGhzProtocolEncoder kia_protocol_v7_encoder;
extern const SubGhzProtocol kia_protocol_v7;
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment);
void kia_protocol_decoder_v7_free(void* context);
void kia_protocol_decoder_v7_reset(void* context);
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration);
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context);
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
void kia_protocol_encoder_v7_free(void* context);
SubGhzProtocolStatus
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
void kia_protocol_encoder_v7_stop(void* context);
LevelDuration kia_protocol_encoder_v7_yield(void* context);
+706
View File
@@ -0,0 +1,706 @@
#include "mazda_v0.h"
#include <string.h>
// =============================================================================
// PROTOCOL CONSTANTS
// =============================================================================
static const SubGhzBlockConst subghz_protocol_mazda_v0_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
#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 DEFINITIONS
// =============================================================================
typedef struct SubGhzProtocolDecoderMazdaV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint16_t preamble_count;
uint8_t preamble_pattern;
uint32_t serial;
uint8_t button;
uint32_t count;
} SubGhzProtocolDecoderMazdaV0;
typedef struct SubGhzProtocolEncoderMazdaV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t serial;
uint8_t button;
uint32_t count;
} SubGhzProtocolEncoderMazdaV0;
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);
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);
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,
};
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 SubGhzProtocol mazda_v0_protocol = {
.name = MAZDA_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_mazda_v0_decoder,
.encoder = &subghz_protocol_mazda_v0_encoder,
};
// =============================================================================
// HELPERS
// =============================================================================
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;
}
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;
}
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
// =============================================================================
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_encoder_mazda_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderMazdaV0* instance = context;
free(instance->encoder.upload);
free(instance);
}
SubGhzProtocolStatus
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
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_encoder_mazda_v0_stop(void* context) {
furi_check(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;
}
// =============================================================================
// 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 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->preamble_pattern = 0;
}
break;
case MazdaV0DecoderStepPreamble:
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)) {
instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0);
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;
}
}
break;
}
}
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
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);
if(ret == SubGhzProtocolStatusOk) {
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;
}
SubGhzProtocolStatus
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
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 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,
(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);
}
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.h>
#include <lib/toolbox/level_duration.h>
#include <lib/toolbox/manchester_decoder.h>
#define MAZDA_PROTOCOL_V0_NAME "Mazda V0"
extern const SubGhzProtocol mazda_v0_protocol;
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_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);
+321
View File
@@ -0,0 +1,321 @@
#include "mitsubishi_v0.h"
#include <inttypes.h>
#define TAG "MitsubishiProtocolV0"
#define MITSUBISHI_V0_PREAMBLE_COUNT 100
#define MITSUBISHI_V0_BIT_TE 250
#define MITSUBISHI_V0_BIT_TE_GAP 500
#define MITSUBISHI_V0_BIT_COUNT 96 // 12 bytes * 8 bits
#define MITSUBISHI_V0_TOTAL_BURSTS 3
#define MITSUBISHI_V0_INTER_BURST_GAP 25000
static const SubGhzBlockConst subghz_protocol_mitsubishi_v0_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 80,
};
struct SubGhzProtocolDecoderMitsubishiV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint32_t te_last;
uint8_t bit_count;
uint8_t decode_data[12];
};
struct SubGhzProtocolEncoderMitsubishiV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
size_t upload_capacity;
};
// ============================================================================
// HELPER FUNCTIONS (Scrambling & Logic)
// ============================================================================
static void mitsubishi_v0_scramble(uint8_t* payload, uint16_t counter) {
uint8_t hi = (counter >> 8) & 0xFF;
uint8_t lo = counter & 0xFF;
uint8_t mask1 = (hi & 0xAA) | (lo & 0x55);
uint8_t mask2 = (lo & 0xAA) | (hi & 0x55);
uint8_t mask3 = mask1 ^ mask2;
// Apply scrambling to first 5 bytes (as per sub_ROM_148BE @ 0x148BE)
for(int i = 0; i < 5; i++) {
payload[i] ^= mask3;
}
// Apply inversion (first 8 bytes) — firmware XORs bytes 1..8 with 0xFF in sub_ROM_151E8
for(int i = 0; i < 8; i++) {
payload[i] = ~payload[i];
}
}
// ============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// ============================================================================
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_v0_decoder = {
.alloc = subghz_protocol_decoder_mitsubishi_v0_alloc,
.free = subghz_protocol_decoder_mitsubishi_v0_free,
.feed = subghz_protocol_decoder_mitsubishi_v0_feed,
.reset = subghz_protocol_decoder_mitsubishi_v0_reset,
.get_hash_data = subghz_protocol_decoder_mitsubishi_v0_get_hash_data,
.serialize = subghz_protocol_decoder_mitsubishi_v0_serialize,
.deserialize = subghz_protocol_decoder_mitsubishi_v0_deserialize,
.get_string = subghz_protocol_decoder_mitsubishi_v0_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_v0_encoder = {
.alloc = subghz_protocol_encoder_mitsubishi_v0_alloc,
.free = subghz_protocol_encoder_mitsubishi_v0_free,
.deserialize = subghz_protocol_encoder_mitsubishi_v0_deserialize,
.stop = subghz_protocol_encoder_mitsubishi_v0_stop,
.yield = subghz_protocol_encoder_mitsubishi_v0_yield,
};
const SubGhzProtocol subghz_protocol_mitsubishi_v0 = {
.name = MITSUBISHI_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_mitsubishi_v0_decoder,
.encoder = &subghz_protocol_mitsubishi_v0_encoder,
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_encoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMitsubishiV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 5;
instance->encoder.size_upload = 0;
// Preamble + Sync + (12 bytes * 8 bits * 2 elements) + Gap
instance->upload_capacity = (MITSUBISHI_V0_PREAMBLE_COUNT * 2) + 20 + (MITSUBISHI_V0_BIT_COUNT * 2) + 2;
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
return instance;
}
void subghz_protocol_encoder_mitsubishi_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderMitsubishiV0* instance = context;
if(instance->encoder.upload) free(instance->encoder.upload);
free(instance);
}
static void subghz_protocol_encoder_mitsubishi_v0_get_upload(SubGhzProtocolEncoderMitsubishiV0* instance) {
size_t index = 0;
uint8_t payload[12] = {0};
// Pack data
payload[0] = (instance->generic.serial >> 24) & 0xFF;
payload[1] = (instance->generic.serial >> 16) & 0xFF;
payload[2] = (instance->generic.serial >> 8) & 0xFF;
payload[3] = instance->generic.serial & 0xFF;
payload[4] = (instance->generic.cnt >> 8) & 0xFF;
payload[5] = instance->generic.cnt & 0xFF;
payload[6] = instance->generic.btn;
payload[9] = 0x5A; // ID byte (firmware: byte_RAM_59 = 0x5A in sub_ROM_151E8 @ 0x15258)
payload[10] = 0xFF;
payload[11] = 0xFF;
mitsubishi_v0_scramble(payload, (uint16_t)instance->generic.cnt);
// Preamble
for(int i = 0; i < MITSUBISHI_V0_PREAMBLE_COUNT; i++) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
// Sync pulses (firmware: 96-iteration loop in sub_ROM_1526C @ 0x152A0)
for(int i = 0; i < 95; i++) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
// Data bits
for(int i = 0; i < 12; i++) {
for(int bit = 7; bit >= 0; bit--) {
bool curr = (payload[i] >> bit) & 1;
if(curr) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE_GAP);
} else {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE_GAP);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
}
}
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
SubGhzProtocolStatus
subghz_protocol_encoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* flipper_format) {
SubGhzProtocolEncoderMitsubishiV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
if(!flipper_format_rewind(flipper_format)) break;
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_temp = 0;
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
instance->generic.btn = (uint8_t)btn_temp;
subghz_protocol_encoder_mitsubishi_v0_get_upload(instance);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_mitsubishi_v0_stop(void* context) {
((SubGhzProtocolEncoderMitsubishiV0*)context)->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_mitsubishi_v0_yield(void* context) {
SubGhzProtocolEncoderMitsubishiV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) return level_duration_reset();
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishiV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_mitsubishi_v0_free(void* context) {
free(context);
}
void subghz_protocol_decoder_mitsubishi_v0_reset(void* context) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
instance->bit_count = 0;
memset(instance->decode_data, 0, 12);
}
void subghz_protocol_decoder_mitsubishi_v0_feed(void* context, bool level, uint32_t duration) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
// Simplified Pulse Distance/Width Decoder
uint32_t te = subghz_protocol_mitsubishi_v0_const.te_short;
uint32_t te2 = subghz_protocol_mitsubishi_v0_const.te_long;
uint32_t delta = subghz_protocol_mitsubishi_v0_const.te_delta;
if(!level) {
// Logic '1': HIGH 250, LOW 500
// Logic '0': HIGH 500, LOW 250
if(DURATION_DIFF(instance->te_last, te) < delta && DURATION_DIFF(duration, te2) < delta) {
// bit 1
instance->decode_data[instance->bit_count / 8] |= (1 << (7 - (instance->bit_count % 8)));
instance->bit_count++;
} else if(DURATION_DIFF(instance->te_last, te2) < delta && DURATION_DIFF(duration, te) < delta) {
// bit 0
instance->bit_count++;
} else {
instance->bit_count = 0;
memset(instance->decode_data, 0, 12);
}
if(instance->bit_count == MITSUBISHI_V0_BIT_COUNT) {
// Un-scramble for display
uint8_t payload[12];
memcpy(payload, instance->decode_data, 12);
// Undo Inversion
for(int i = 0; i < 8; i++) payload[i] = ~payload[i];
// We need the counter to unscramble (bytes 4-5)
uint16_t counter = (payload[4] << 8) | payload[5];
// Undo Scrambling
uint8_t hi = (counter >> 8) & 0xFF;
uint8_t lo = counter & 0xFF;
uint8_t m1 = (hi & 0xAA) | (lo & 0x55);
uint8_t m2 = (lo & 0xAA) | (hi & 0x55);
uint8_t m3 = m1 ^ m2;
for(int i = 0; i < 5; i++) payload[i] ^= m3;
instance->generic.serial = (payload[0] << 24) | (payload[1] << 16) | (payload[2] << 8) | payload[3];
instance->generic.cnt = counter;
instance->generic.btn = payload[6];
instance->generic.data_count_bit = instance->bit_count;
if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context);
instance->bit_count = 0;
}
}
instance->te_last = duration;
}
uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
uint8_t hash = 0;
for(size_t i = 0; i < 12; i++) {
hash ^= instance->decode_data[i];
}
return hash;
}
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_serialize(
void* context,
FlipperFormat* ff,
SubGhzRadioPreset* preset) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, ff, preset);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_write_uint32(ff, "Serial", &instance->generic.serial, 1);
flipper_format_write_uint32(ff, "Cnt", &instance->generic.cnt, 1);
uint32_t btn = instance->generic.btn;
flipper_format_write_uint32(ff, "Btn", &btn, 1);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* ff) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
return subghz_block_generic_deserialize_check_count_bit(&instance->generic, ff, subghz_protocol_mitsubishi_v0_const.min_count_bit_for_found);
}
void subghz_protocol_decoder_mitsubishi_v0_get_string(void* context, FuriString* output) {
SubGhzProtocolDecoderMitsubishiV0* 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);
}
+168 -224
View File
@@ -1,63 +1,110 @@
#include "mitsubishi_v0.h"
#include <inttypes.h>
#include <string.h>
#define TAG "MitsubishiProtocolV0"
#define MITSUBISHI_V0_PREAMBLE_COUNT 100
#define MITSUBISHI_V0_BIT_TE 250
#define MITSUBISHI_V0_BIT_TE_GAP 500
#define MITSUBISHI_V0_BIT_COUNT 96 // 12 bytes * 8 bits
#define MITSUBISHI_V0_TOTAL_BURSTS 3
#define MITSUBISHI_V0_INTER_BURST_GAP 25000
// Original implementation by @lupettohf
static const SubGhzBlockConst subghz_protocol_mitsubishi_v0_const = {
#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,
};
struct SubGhzProtocolDecoderMitsubishiV0 {
typedef enum {
MitsubishiDecoderStepReset = 0,
MitsubishiDecoderStepDataSave,
MitsubishiDecoderStepDataCheck,
} MitsubishiDecoderStep;
typedef struct SubGhzProtocolDecoderMitsubishi SubGhzProtocolDecoderMitsubishi;
struct SubGhzProtocolDecoderMitsubishi {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint32_t te_last;
uint8_t bit_count;
uint8_t decode_data[12];
uint8_t decoder_state;
uint16_t bit_count;
uint8_t decode_data[MITSUBISHI_DATA_BYTES];
};
struct SubGhzProtocolEncoderMitsubishiV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
size_t upload_capacity;
};
// ============================================================================
// HELPER FUNCTIONS (Scrambling & Logic)
// ============================================================================
static void mitsubishi_v0_scramble(uint8_t* payload, uint16_t counter) {
uint8_t hi = (counter >> 8) & 0xFF;
uint8_t lo = counter & 0xFF;
uint8_t mask1 = (hi & 0xAA) | (lo & 0x55);
uint8_t mask2 = (lo & 0xAA) | (hi & 0x55);
uint8_t mask3 = mask1 ^ mask2;
// Apply scrambling to first 5 bytes (as per sub_ROM_148BE @ 0x148BE)
for(int i = 0; i < 5; i++) {
payload[i] ^= mask3;
static void mitsubishi_unscramble_payload(uint8_t* payload) {
for(uint8_t i = 0; i < 8; i++) {
payload[i] = (uint8_t)~payload[i];
}
// Apply inversion (first 8 bytes) — firmware XORs bytes 1..8 with 0xFF in sub_ROM_151E8
for(int i = 0; i < 8; i++) {
payload[i] = ~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;
}
}
// ============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// ============================================================================
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_v0_decoder = {
.alloc = subghz_protocol_decoder_mitsubishi_v0_alloc,
@@ -71,141 +118,25 @@ const SubGhzProtocolDecoder subghz_protocol_mitsubishi_v0_decoder = {
};
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_v0_encoder = {
.alloc = subghz_protocol_encoder_mitsubishi_v0_alloc,
.free = subghz_protocol_encoder_mitsubishi_v0_free,
.deserialize = subghz_protocol_encoder_mitsubishi_v0_deserialize,
.stop = subghz_protocol_encoder_mitsubishi_v0_stop,
.yield = subghz_protocol_encoder_mitsubishi_v0_yield,
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol subghz_protocol_mitsubishi_v0 = {
.name = MITSUBISHI_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.decoder = &subghz_protocol_mitsubishi_v0_decoder,
.encoder = &subghz_protocol_mitsubishi_v0_encoder,
};
// ============================================================================
// ENCODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_encoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMitsubishiV0));
furi_check(instance);
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 5;
instance->encoder.size_upload = 0;
// Preamble + Sync + (12 bytes * 8 bits * 2 elements) + Gap
instance->upload_capacity = (MITSUBISHI_V0_PREAMBLE_COUNT * 2) + 20 + (MITSUBISHI_V0_BIT_COUNT * 2) + 2;
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
return instance;
}
void subghz_protocol_encoder_mitsubishi_v0_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderMitsubishiV0* instance = context;
if(instance->encoder.upload) free(instance->encoder.upload);
free(instance);
}
static void subghz_protocol_encoder_mitsubishi_v0_get_upload(SubGhzProtocolEncoderMitsubishiV0* instance) {
size_t index = 0;
uint8_t payload[12] = {0};
// Pack data
payload[0] = (instance->generic.serial >> 24) & 0xFF;
payload[1] = (instance->generic.serial >> 16) & 0xFF;
payload[2] = (instance->generic.serial >> 8) & 0xFF;
payload[3] = instance->generic.serial & 0xFF;
payload[4] = (instance->generic.cnt >> 8) & 0xFF;
payload[5] = instance->generic.cnt & 0xFF;
payload[6] = instance->generic.btn;
payload[9] = 0x5A; // ID byte (firmware: byte_RAM_59 = 0x5A in sub_ROM_151E8 @ 0x15258)
payload[10] = 0xFF;
payload[11] = 0xFF;
mitsubishi_v0_scramble(payload, (uint16_t)instance->generic.cnt);
// Preamble
for(int i = 0; i < MITSUBISHI_V0_PREAMBLE_COUNT; i++) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
// Sync pulses (firmware: 96-iteration loop in sub_ROM_1526C @ 0x152A0)
for(int i = 0; i < 95; i++) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
// Data bits
for(int i = 0; i < 12; i++) {
for(int bit = 7; bit >= 0; bit--) {
bool curr = (payload[i] >> bit) & 1;
if(curr) {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE_GAP);
} else {
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE_GAP);
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
}
}
}
instance->encoder.size_upload = index;
instance->encoder.front = 0;
}
SubGhzProtocolStatus
subghz_protocol_encoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* flipper_format) {
SubGhzProtocolEncoderMitsubishiV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
if(!flipper_format_rewind(flipper_format)) break;
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_temp = 0;
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
instance->generic.btn = (uint8_t)btn_temp;
subghz_protocol_encoder_mitsubishi_v0_get_upload(instance);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_mitsubishi_v0_stop(void* context) {
((SubGhzProtocolEncoderMitsubishiV0*)context)->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_mitsubishi_v0_yield(void* context) {
SubGhzProtocolEncoderMitsubishiV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) return level_duration_reset();
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// DECODER IMPLEMENTATION
// ============================================================================
void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishiV0));
SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi));
furi_check(instance);
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
instance->generic.protocol_name = instance->base.protocol->name;
@@ -213,73 +144,68 @@ void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment
}
void subghz_protocol_decoder_mitsubishi_v0_free(void* context) {
free(context);
furi_check(context);
SubGhzProtocolDecoderMitsubishi* instance = context;
free(instance);
}
void subghz_protocol_decoder_mitsubishi_v0_reset(void* context) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
instance->bit_count = 0;
memset(instance->decode_data, 0, 12);
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_v0_feed(void* context, bool level, uint32_t duration) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
furi_check(context);
SubGhzProtocolDecoderMitsubishi* instance = context;
// Simplified Pulse Distance/Width Decoder
uint32_t te = subghz_protocol_mitsubishi_v0_const.te_short;
uint32_t te2 = subghz_protocol_mitsubishi_v0_const.te_long;
uint32_t delta = subghz_protocol_mitsubishi_v0_const.te_delta;
switch(instance->decoder_state) {
case MitsubishiDecoderStepReset:
if(level) {
instance->decoder.te_last = duration;
instance->decoder_state = MitsubishiDecoderStepDataCheck;
}
break;
if(!level) {
// Logic '1': HIGH 250, LOW 500
// Logic '0': HIGH 500, LOW 250
if(DURATION_DIFF(instance->te_last, te) < delta && DURATION_DIFF(duration, te2) < delta) {
// bit 1
instance->decode_data[instance->bit_count / 8] |= (1 << (7 - (instance->bit_count % 8)));
instance->bit_count++;
} else if(DURATION_DIFF(instance->te_last, te2) < delta && DURATION_DIFF(duration, te) < delta) {
// bit 0
instance->bit_count++;
case MitsubishiDecoderStepDataSave:
if(level) {
instance->decoder.te_last = duration;
instance->decoder_state = MitsubishiDecoderStepDataCheck;
} else {
instance->bit_count = 0;
memset(instance->decode_data, 0, 12);
instance->decoder_state = MitsubishiDecoderStepReset;
mitsubishi_reset_payload(instance);
}
break;
if(instance->bit_count == MITSUBISHI_V0_BIT_COUNT) {
// Un-scramble for display
uint8_t payload[12];
memcpy(payload, instance->decode_data, 12);
// Undo Inversion
for(int i = 0; i < 8; i++) payload[i] = ~payload[i];
// We need the counter to unscramble (bytes 4-5)
uint16_t counter = (payload[4] << 8) | payload[5];
// Undo Scrambling
uint8_t hi = (counter >> 8) & 0xFF;
uint8_t lo = counter & 0xFF;
uint8_t m1 = (hi & 0xAA) | (lo & 0x55);
uint8_t m2 = (lo & 0xAA) | (hi & 0x55);
uint8_t m3 = m1 ^ m2;
for(int i = 0; i < 5; i++) payload[i] ^= m3;
instance->generic.serial = (payload[0] << 24) | (payload[1] << 16) | (payload[2] << 8) | payload[3];
instance->generic.cnt = counter;
instance->generic.btn = payload[6];
instance->generic.data_count_bit = instance->bit_count;
if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context);
instance->bit_count = 0;
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;
}
instance->te_last = duration;
}
uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
furi_check(context);
SubGhzProtocolDecoderMitsubishi* instance = context;
uint8_t hash = 0;
for(size_t i = 0; i < 12; i++) {
for(size_t i = 0; i < sizeof(instance->decode_data); i++) {
hash ^= instance->decode_data[i];
}
return hash;
@@ -287,26 +213,44 @@ uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_serialize(
void* context,
FlipperFormat* ff,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, ff, 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(ff, "Serial", &instance->generic.serial, 1);
flipper_format_write_uint32(ff, "Cnt", &instance->generic.cnt, 1);
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(ff, "Btn", &btn, 1);
flipper_format_write_uint32(flipper_format, "Btn", &btn, 1);
}
return ret;
}
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* ff) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
return subghz_block_generic_deserialize_check_count_bit(&instance->generic, ff, subghz_protocol_mitsubishi_v0_const.min_count_bit_for_found);
SubGhzProtocolStatus
subghz_protocol_decoder_mitsubishi_v0_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_v0_get_string(void* context, FuriString* output) {
SubGhzProtocolDecoderMitsubishiV0* instance = context;
furi_check(context);
SubGhzProtocolDecoderMitsubishi* instance = context;
furi_string_cat_printf(
output,
+374
View File
@@ -0,0 +1,374 @@
#include "porsche_touareg.h"
#include <string.h>
// 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));
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <flipper_format/flipper_format.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);
+10
View File
@@ -75,6 +75,16 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_star_line,
&subghz_protocol_scher_khan,
&subghz_protocol_sheriff_cfm,
&subghz_protocol_renault_hitag,
&subghz_protocol_renault_siemens,
&subghz_protocol_renault_valeo,
&subghz_protocol_renault_valeo_fsk,
&subghz_protocol_renault_marelli,
&subghz_protocol_honda_v1,
&ford_protocol_v1,
&honda_static_protocol,
&fiat_protocol_v0,
&subghz_protocol_fiat_v1,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {
+10
View File
@@ -77,3 +77,13 @@
#include "star_line.h"
#include "scher_khan.h"
#include "sheriff_cfm.h"
#include "renault_hitag.h"
#include "renault_siemens.h"
#include "renault_valeo.h"
#include "renault_valeo_fsk.h"
#include "renault_marelli.h"
#include "ford_v1.h"
#include "honda_v1.h"
#include "honda_static.h"
#include "fiat_v0.h"
#include "fiat_v1.h"
File diff suppressed because it is too large Load Diff
+1283 -1168
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
#include "renault_classifier.h"
#include <stdint.h>
// Hitag: 4063 bits
// Siemens: 6480 bits
// Valeo: 8196 bits
// Marelli: 97110 bits
RenaultProtocolType renault_classify(uint8_t bits) {
if(bits >= 40 && bits <= 63) return RenaultProtoHitag;
if(bits >= 64 && bits <= 80) return RenaultProtoSiemens;
if(bits >= 81 && bits <= 96) return RenaultProtoValeo;
if(bits >= 97 && bits <= 110) return RenaultProtoMarelli;
return RenaultProtoUnknown;
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <stdint.h>
typedef enum {
RenaultProtoUnknown,
RenaultProtoHitag,
RenaultProtoSiemens,
RenaultProtoValeo,
RenaultProtoMarelli,
} RenaultProtocolType;
RenaultProtocolType renault_classify(uint8_t bits);
+292
View File
@@ -0,0 +1,292 @@
#include "renault_hitag.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#define TAG "RenaultHitag"
// Hitag2 / PCF7936 keyfob — OOK PWM
// te_short ≈ 200 µs (bit 0 mark)
// te_long ≈ 400 µs (bit 1 mark)
// Space between bits ≈ te_short
// Gap between frames > 3 × te_long
#define HITAG_TE_SHORT 200
#define HITAG_TE_LONG 400
#define HITAG_TE_DELTA 120
#define HITAG_MIN_BITS 40
#define HITAG_MAX_BITS 80
#define HITAG_GAP_MIN (HITAG_TE_LONG * 3)
typedef enum {
HitagStepReset = 0,
HitagStepWaitMark,
HitagStepWaitSpace,
} HitagStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
} RenaultHitagDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t hitag_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
static bool renault_hitag_button_is_valid(uint8_t btn) {
// Keep permissive set for newer variants but reject obvious garbage values.
return (btn <= 0x0B) || (btn == 0x0D);
}
static bool renault_hitag_frame_is_plausible(RenaultHitagDecoder* inst) {
if(inst->bit_count < HITAG_MIN_BITS || inst->bit_count > HITAG_MAX_BITS) {
return false;
}
// Typical packet sizes observed in the field.
if(inst->bit_count < 48U) {
if(inst->bit_count != 40U) return false;
} else if((inst->bit_count % 4U) != 0U) {
return false;
}
const uint8_t btn = (uint8_t)((inst->data >> (inst->bit_count - 4U)) & 0x0FU);
if(!renault_hitag_button_is_valid(btn)) {
return false;
}
const uint32_t serial = (inst->bit_count >= 48U) ?
(uint32_t)((inst->data >> 16U) & 0x0FFFFFFFU) :
(uint32_t)((inst->data >> 12U) & 0x00FFFFFFU);
return serial != 0U;
}
static void renault_hitag_extract_fields(RenaultHitagDecoder* inst) {
uint8_t total = inst->generic.data_count_bit;
if(total >= 48) {
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
inst->generic.serial = (uint32_t)((inst->generic.data >> 16) & 0x0FFFFFFF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
} else if(total >= 40) {
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
inst->generic.serial = (uint32_t)((inst->generic.data >> 12) & 0x0FFFFFF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0x0FFF);
} else {
inst->generic.btn = 0;
inst->generic.serial = (uint32_t)(inst->generic.data >> 16);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
}
}
static void renault_hitag_try_accept(RenaultHitagDecoder* inst) {
if(renault_hitag_frame_is_plausible(inst)) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_hitag_extract_fields(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_hitag_alloc(SubGhzEnvironment* env) {
UNUSED(env);
RenaultHitagDecoder* inst = malloc(sizeof(RenaultHitagDecoder));
memset(inst, 0, sizeof(RenaultHitagDecoder));
inst->base.protocol = &subghz_protocol_renault_hitag;
inst->generic.protocol_name = inst->base.protocol->name;
return inst;
}
static void renault_hitag_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_hitag_reset(void* ctx) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
inst->bit_count = 0;
inst->data = 0;
inst->parser_step = HitagStepReset;
}
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
static void renault_hitag_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
switch(inst->parser_step) {
case HitagStepReset:
if(level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
}
}
break;
case HitagStepWaitSpace:
if(!level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->parser_step = HitagStepWaitMark;
} else if(duration >= HITAG_GAP_MIN) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
} else {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
break;
case HitagStepWaitMark:
if(level) {
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = HitagStepWaitSpace;
} else {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
} else {
if(duration >= HITAG_GAP_MIN) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
break;
default:
renault_hitag_reset(ctx);
break;
}
if(inst->bit_count > HITAG_MAX_BITS) {
renault_hitag_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = HitagStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_hitag_get_hash(void* ctx) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_hitag_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
static SubGhzProtocolStatus
renault_hitag_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, HITAG_MIN_BITS);
if(ret == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
renault_hitag_extract_fields(inst);
}
return ret;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_hitag_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultHitagDecoder* inst = ctx;
renault_hitag_extract_fields(inst);
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
const SubGhzProtocolDecoder renault_hitag_decoder = {
.alloc = renault_hitag_alloc,
.free = renault_hitag_free,
.feed = renault_hitag_feed,
.reset = renault_hitag_reset,
.get_hash_data = renault_hitag_get_hash,
.serialize = renault_hitag_serialize,
.deserialize = renault_hitag_deserialize,
.get_string = renault_hitag_get_string,
};
const SubGhzProtocol subghz_protocol_renault_hitag = {
.name = SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_hitag_decoder,
.encoder = NULL,
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME "Renault_Hitag"
extern const SubGhzProtocol subghz_protocol_renault_hitag;
+696
View File
@@ -0,0 +1,696 @@
#include "renault_marelli.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#include "../blocks/custom_btn_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <lib/toolbox/manchester_encoder.h>
#include <furi_hal_subghz.h>
#define TAG "RenaultMarelli"
// Magneti Marelli BSI keyfob protocol (PCF7946) — Renault variant
// Found on: Renault Clio III, Modus, Kangoo II, and some Dacia models
// sharing the Fiat/Renault Marelli platform (~2004-2014)
//
// RF: 433.92 MHz, Manchester encoding (FSK modulation)
// Two timing variants with identical frame structure:
// Type A (standard): te_short ~260us, te_long ~520us
// Type B (fast/compact): 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)
#define REN_MARELLI_PREAMBLE_PULSE_MIN 50
#define REN_MARELLI_PREAMBLE_PULSE_MAX 350
#define REN_MARELLI_PREAMBLE_MIN 80
#define REN_MARELLI_MAX_DATA_BITS 104
#define REN_MARELLI_MIN_DATA_BITS 80
#define REN_MARELLI_GAP_TE_MULT 4
#define REN_MARELLI_SYNC_TE_MIN_MULT 4
#define REN_MARELLI_SYNC_TE_MAX_MULT 12
#define REN_MARELLI_RETX_GAP_MIN 5000
#define REN_MARELLI_RETX_SYNC_MIN 400
#define REN_MARELLI_RETX_SYNC_MAX 2800
#define REN_MARELLI_TE_TYPE_AB_BOUNDARY 180
static const SubGhzBlockConst subghz_protocol_renault_marelli_const = {
.te_short = 260,
.te_long = 520,
.te_delta = 80,
.min_count_bit_for_found = 80,
};
struct SubGhzProtocolDecoderRenaultMarelli {
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;
};
struct SubGhzProtocolEncoderRenaultMarelli {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint8_t raw_data[13];
uint32_t extra_data;
uint8_t bit_count;
uint32_t te_detected;
};
typedef enum {
RenMarelliDecoderStepReset = 0,
RenMarelliDecoderStepPreamble = 1,
RenMarelliDecoderStepSync = 2,
RenMarelliDecoderStepData = 3,
RenMarelliDecoderStepRetxSync = 4,
} RenMarelliDecoderStep;
static bool renault_marelli_button_is_renault(uint8_t btn) {
// Renault Marelli: button codes usually seen as 0x1/0x2/0x4.
// Fiat Marelli commonly uses 0x7/0xB/0xD.
return (btn == 0x1) || (btn == 0x2) || (btn == 0x4);
}
static bool renault_marelli_frame_is_renault(const SubGhzProtocolDecoderRenaultMarelli* instance) {
const uint8_t btn = (instance->raw_data[6] >> 4) & 0xF;
const uint8_t fixed = instance->raw_data[7] & 0x1;
const uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
if(!renault_marelli_button_is_renault(btn)) {
return false;
}
// Keep structural guards to reduce false positives from noise.
if(fixed > 1U || scramble > 3U) {
return false;
}
return true;
}
const SubGhzProtocolDecoder subghz_protocol_renault_marelli_decoder = {
.alloc = subghz_protocol_decoder_renault_marelli_alloc,
.free = subghz_protocol_decoder_renault_marelli_free,
.feed = subghz_protocol_decoder_renault_marelli_feed,
.reset = subghz_protocol_decoder_renault_marelli_reset,
.get_hash_data = subghz_protocol_decoder_renault_marelli_get_hash_data,
.serialize = subghz_protocol_decoder_renault_marelli_serialize,
.deserialize = subghz_protocol_decoder_renault_marelli_deserialize,
.get_string = subghz_protocol_decoder_renault_marelli_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_renault_marelli_encoder = {
.alloc = subghz_protocol_encoder_renault_marelli_alloc,
.free = subghz_protocol_encoder_renault_marelli_free,
.deserialize = subghz_protocol_encoder_renault_marelli_deserialize,
.stop = subghz_protocol_encoder_renault_marelli_stop,
.yield = subghz_protocol_encoder_renault_marelli_yield,
};
const SubGhzProtocol subghz_protocol_renault_marelli = {
.name = RENAULT_MARELLI_PROTOCOL_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_renault_marelli_decoder,
.encoder = &subghz_protocol_renault_marelli_encoder,
};
// ============================================================================
// Encoder
// ============================================================================
#define REN_MARELLI_ENCODER_UPLOAD_MAX 1500
#define REN_MARELLI_ENCODER_REPEAT 3
#define REN_MARELLI_PREAMBLE_PAIRS 100
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderRenaultMarelli* instance =
calloc(1, sizeof(SubGhzProtocolEncoderRenaultMarelli));
furi_check(instance);
instance->base.protocol = &subghz_protocol_renault_marelli;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
instance->encoder.size_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
instance->encoder.upload =
malloc(REN_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_renault_marelli_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
free(instance->encoder.upload);
free(instance);
}
static bool renault_marelli_encoder_get_upload(SubGhzProtocolEncoderRenaultMarelli* instance) {
uint32_t te = instance->te_detected;
if(te == 0) te = subghz_protocol_renault_marelli_const.te_short;
uint32_t te_short = te;
uint32_t te_long = te * 2;
uint32_t gap_duration = te * 12;
uint32_t sync_duration = te * 8;
size_t index = 0;
size_t max_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
uint8_t data_bits = instance->bit_count;
if(data_bits == 0) data_bits = instance->generic.data_count_bit;
if(data_bits < REN_MARELLI_MIN_DATA_BITS || data_bits > REN_MARELLI_MAX_DATA_BITS) {
return false;
}
// Preamble: alternating short HIGH/LOW
for(uint8_t i = 0; i < REN_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
if(i < REN_MARELLI_PREAMBLE_PAIRS - 1) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
// Gap after preamble
if(index < max_upload) {
instance->encoder.upload[index++] =
level_duration_make(false, te_short + gap_duration);
}
// Sync pulse
if(index < max_upload) {
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
}
// Manchester-encode data bits
bool in_mid1 = true;
for(uint8_t bit_i = 0; bit_i < data_bits && (index + 1) < max_upload; bit_i++) {
uint8_t byte_idx = bit_i / 8;
uint8_t bit_pos = 7 - (bit_i % 8);
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
if(in_mid1) {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(false, te_short);
instance->encoder.upload[index++] = level_duration_make(true, te_short);
} else {
instance->encoder.upload[index++] = level_duration_make(false, te_long);
in_mid1 = false;
}
} else {
if(data_bit) {
instance->encoder.upload[index++] = level_duration_make(true, te_long);
in_mid1 = true;
} else {
instance->encoder.upload[index++] = level_duration_make(true, te_short);
instance->encoder.upload[index++] = level_duration_make(false, te_short);
}
}
}
// Trailing gap
if(in_mid1) {
if(index < max_upload) {
instance->encoder.upload[index++] =
level_duration_make(false, te_short + gap_duration * 3);
}
} else {
if(index > 0) {
instance->encoder.upload[index - 1] =
level_duration_make(false, te_short + gap_duration * 3);
}
}
instance->encoder.size_upload = index;
return index > 0;
}
static void renault_marelli_encoder_rebuild_raw_data(
SubGhzProtocolEncoderRenaultMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data));
uint64_t key = instance->generic.data;
for(int 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] |= (1 << bit_pos);
}
}
instance->bit_count = instance->generic.data_count_bit;
}
SubGhzProtocolStatus subghz_protocol_encoder_renault_marelli_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
if(ret != SubGhzProtocolStatusOk) break;
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;
}
renault_marelli_encoder_rebuild_raw_data(instance);
if(!renault_marelli_encoder_get_upload(instance)) {
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
break;
}
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
instance->encoder.front = 0;
instance->encoder.is_running = true;
} while(false);
return ret;
}
void subghz_protocol_encoder_renault_marelli_stop(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderRenaultMarelli* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
if(!subghz_block_generic_global.endless_tx) {
instance->encoder.repeat--;
}
instance->encoder.front = 0;
}
return ret;
}
// ============================================================================
// Decoder
// ============================================================================
static void renault_marelli_rebuild_raw_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
memset(instance->raw_data, 0, sizeof(instance->raw_data));
uint64_t key = instance->generic.data;
for(int 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] |= (1 << 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) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
}
}
static void renault_marelli_prepare_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
instance->bit_count = 0;
instance->extra_data = 0;
instance->generic.data = 0;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
manchester_advance(
instance->manchester_state,
ManchesterEventReset,
&instance->manchester_state,
NULL);
instance->decoder_state = RenMarelliDecoderStepData;
}
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderRenaultMarelli* instance =
calloc(1, sizeof(SubGhzProtocolDecoderRenaultMarelli));
furi_check(instance);
instance->base.protocol = &subghz_protocol_renault_marelli;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_renault_marelli_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
free(instance);
}
void subghz_protocol_decoder_renault_marelli_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
instance->decoder_state = RenMarelliDecoderStepReset;
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;
memset(instance->raw_data, 0, sizeof(instance->raw_data));
instance->manchester_state = ManchesterStateMid1;
}
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
uint32_t te_short = instance->te_detected
? instance->te_detected
: (uint32_t)subghz_protocol_renault_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 RenMarelliDecoderStepReset:
if(level) {
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= REN_MARELLI_PREAMBLE_PULSE_MAX) {
instance->decoder_state = RenMarelliDecoderStepPreamble;
instance->preamble_count = 1;
instance->te_sum = duration;
instance->te_count = 1;
instance->te_last = duration;
}
} else {
if(duration > REN_MARELLI_RETX_GAP_MIN) {
instance->decoder_state = RenMarelliDecoderStepRetxSync;
instance->te_last = duration;
}
}
break;
case RenMarelliDecoderStepPreamble:
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
duration <= REN_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 >= REN_MARELLI_PREAMBLE_MIN &&
instance->te_count > 0) {
instance->te_detected = instance->te_sum / instance->te_count;
uint32_t gap_threshold =
instance->te_detected * REN_MARELLI_GAP_TE_MULT;
if(duration > gap_threshold) {
instance->decoder_state = RenMarelliDecoderStepSync;
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
case RenMarelliDecoderStepSync: {
uint32_t sync_min = instance->te_detected * REN_MARELLI_SYNC_TE_MIN_MULT;
uint32_t sync_max = instance->te_detected * REN_MARELLI_SYNC_TE_MAX_MULT;
if(level && duration >= sync_min && duration <= sync_max) {
renault_marelli_prepare_data(instance);
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
}
case RenMarelliDecoderStepRetxSync:
if(level && duration >= REN_MARELLI_RETX_SYNC_MIN &&
duration <= REN_MARELLI_RETX_SYNC_MAX) {
if(!instance->te_detected) {
instance->te_detected = duration / 8;
if(instance->te_detected < 70) instance->te_detected = 100;
if(instance->te_detected > 350) instance->te_detected = 260;
}
renault_marelli_prepare_data(instance);
instance->te_last = duration;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
break;
case RenMarelliDecoderStepData: {
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;
if(manchester_advance(
instance->manchester_state,
event,
&instance->manchester_state,
&data_bit)) {
uint32_t new_bit = data_bit ? 1 : 0;
if(instance->bit_count < REN_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] |= (1 << 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 >= REN_MARELLI_MAX_DATA_BITS) {
frame_complete = true;
}
}
} else {
if(instance->bit_count >= REN_MARELLI_MIN_DATA_BITS) {
frame_complete = true;
} else {
instance->decoder_state = RenMarelliDecoderStepReset;
}
}
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) & 0xF;
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
if(renault_marelli_frame_is_renault(instance) && instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder_state = RenMarelliDecoderStepReset;
}
instance->te_last = duration;
break;
}
}
}
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit =
instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
};
return subghz_protocol_blocks_get_hash_data(
&decoder, (decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* 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_renault_marelli_deserialize(
void* context,
FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
SubGhzProtocolStatus ret =
subghz_block_generic_deserialize(&instance->generic, flipper_format);
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;
}
renault_marelli_rebuild_raw_data(instance);
}
return ret;
}
static const char* renault_marelli_button_name(uint8_t btn) {
switch(btn) {
case 0x1:
return "Lock";
case 0x2:
return "Unlock";
case 0x4:
return "Trunk";
case 0x7:
return "Lock";
case 0xB:
return "Unlock";
case 0xD:
return "Trunk";
default:
return "Unknown";
}
}
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderRenaultMarelli* instance = context;
uint8_t epoch = instance->raw_data[6] & 0xF;
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
const char* variant = (instance->te_detected &&
instance->te_detected < REN_MARELLI_TE_TYPE_AB_BOUNDARY)
? "B"
: "A";
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
uint8_t fixed = instance->raw_data[7] & 0x1;
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\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,
renault_marelli_button_name(instance->generic.btn),
(unsigned)epoch,
variant);
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "base.h"
#include <flipper_format/flipper_format.h>
#define RENAULT_MARELLI_PROTOCOL_NAME "Ren_MARELLI"
typedef struct SubGhzProtocolDecoderRenaultMarelli SubGhzProtocolDecoderRenaultMarelli;
typedef struct SubGhzProtocolEncoderRenaultMarelli SubGhzProtocolEncoderRenaultMarelli;
extern const SubGhzProtocol subghz_protocol_renault_marelli;
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_renault_marelli_free(void* context);
void subghz_protocol_decoder_renault_marelli_reset(void* context);
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_renault_marelli_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_renault_marelli_stop(void* context);
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context);
+275
View File
@@ -0,0 +1,275 @@
#include "renault_siemens.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#define TAG "RenaultSiemens"
// Siemens VDO keyfob — OOK PWM
// te_short ≈ 250 µs (bit 0 mark)
// te_long ≈ 500 µs (bit 1 mark)
// Space ≈ te_short
// Gap > 3 × te_long
#define SIEMENS_TE_SHORT 250
#define SIEMENS_TE_LONG 500
#define SIEMENS_TE_DELTA 120
#define SIEMENS_MIN_BITS 64
#define SIEMENS_MAX_BITS 96
#define SIEMENS_GAP_MIN (SIEMENS_TE_LONG * 3)
typedef enum {
SiemensStepReset = 0,
SiemensStepWaitMark,
SiemensStepWaitSpace,
} SiemensStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
} RenaultSiemensDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t siemens_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
static bool renault_siemens_button_is_valid(uint8_t btn) {
// Conservative acceptance window for common Renault button nibbles.
return (btn <= 0x0D) && (btn != 0x0C);
}
static bool renault_siemens_frame_is_plausible(RenaultSiemensDecoder* inst) {
if(inst->bit_count < SIEMENS_MIN_BITS || inst->bit_count > SIEMENS_MAX_BITS) {
return false;
}
if((inst->bit_count % 4U) != 0U) {
return false;
}
const uint8_t btn = (uint8_t)((inst->data >> 28U) & 0x0FU);
if(!renault_siemens_button_is_valid(btn)) {
return false;
}
const uint32_t serial = (uint32_t)(inst->data >> 32U);
return serial != 0U;
}
static void renault_siemens_extract_fields(RenaultSiemensDecoder* inst) {
inst->generic.serial = (uint32_t)(inst->generic.data >> 32);
inst->generic.btn = (uint8_t)((inst->generic.data >> 28) & 0xF);
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
}
static void renault_siemens_try_accept(RenaultSiemensDecoder* inst) {
if(renault_siemens_frame_is_plausible(inst)) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_siemens_extract_fields(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_siemens_alloc(SubGhzEnvironment* env) {
UNUSED(env);
RenaultSiemensDecoder* inst = malloc(sizeof(RenaultSiemensDecoder));
memset(inst, 0, sizeof(RenaultSiemensDecoder));
inst->base.protocol = &subghz_protocol_renault_siemens;
inst->generic.protocol_name = inst->base.protocol->name;
return inst;
}
static void renault_siemens_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_siemens_reset(void* ctx) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
static void renault_siemens_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
switch(inst->parser_step) {
case SiemensStepReset:
if(level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
}
}
break;
case SiemensStepWaitSpace:
if(!level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->parser_step = SiemensStepWaitMark;
} else if(duration >= SIEMENS_GAP_MIN) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
} else {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
break;
case SiemensStepWaitMark:
if(level) {
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = SiemensStepWaitSpace;
} else {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
} else {
if(duration >= SIEMENS_GAP_MIN) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
break;
default:
renault_siemens_reset(ctx);
break;
}
if(inst->bit_count > SIEMENS_MAX_BITS) {
renault_siemens_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = SiemensStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_siemens_get_hash(void* ctx) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_siemens_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
}
static SubGhzProtocolStatus
renault_siemens_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, SIEMENS_MIN_BITS);
if(ret == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
renault_siemens_extract_fields(inst);
}
return ret;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_siemens_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultSiemensDecoder* inst = ctx;
renault_siemens_extract_fields(inst);
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%08lX Btn:%X\r\n"
"Cnt:%04lX\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
const SubGhzProtocolDecoder renault_siemens_decoder = {
.alloc = renault_siemens_alloc,
.free = renault_siemens_free,
.feed = renault_siemens_feed,
.reset = renault_siemens_reset,
.get_hash_data = renault_siemens_get_hash,
.serialize = renault_siemens_serialize,
.deserialize = renault_siemens_deserialize,
.get_string = renault_siemens_get_string,
};
const SubGhzProtocol subghz_protocol_renault_siemens = {
.name = SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_siemens_decoder,
.encoder = NULL,
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME "Renault_Siemens"
extern const SubGhzProtocol subghz_protocol_renault_siemens;
+362
View File
@@ -0,0 +1,362 @@
#include "renault_valeo.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#include "keeloq_common.h"
#include "../subghz_keystore.h"
#include "../subghz_keystore_i.h"
#include <m-array.h>
#define TAG "RenaultValeo"
// Valeo OOK keyfob — Captur 2017 / Clio IV / PCF7961
// OOK PWM encoding:
// te_short ≈ 66 µs → bit 0 (mark)
// te_long ≈ 264 µs → bit 1 (mark)
// Space between bits ≈ te_short (66 µs)
// Gap between frames > 500 µs
//
// Trama (64-96 bits):
// [MSB..32] fix: btn[4] + serial[28]
// [31..0] hop: 32 bits KeeLoq encrypted
#define VALEO_TE_SHORT 66
#define VALEO_TE_LONG 264
#define VALEO_TE_DELTA 60
#define VALEO_TE_SHORT_ALT 100
#define VALEO_TE_LONG_ALT 400
#define VALEO_TE_DELTA_ALT 80
#define VALEO_MIN_BITS 64
#define VALEO_MAX_BITS 96
#define VALEO_GAP_MIN 500
typedef enum {
ValeoStepReset = 0,
ValeoStepWaitMark,
ValeoStepWaitSpace,
} ValeoStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t parser_step;
SubGhzKeystore* keystore;
const char* manufacture_name;
} RenaultValeoDecoder;
// ─── Helpers ─────────────────────────────────────────────────────────────────
static inline uint32_t valeo_abs_diff(uint32_t a, uint32_t b) {
return (a > b) ? (a - b) : (b - a);
}
static bool renault_valeo_is_short(uint32_t duration) {
return (valeo_abs_diff(duration, VALEO_TE_SHORT) < VALEO_TE_DELTA) ||
(valeo_abs_diff(duration, VALEO_TE_SHORT_ALT) < VALEO_TE_DELTA_ALT);
}
static bool renault_valeo_is_long(uint32_t duration) {
return (valeo_abs_diff(duration, VALEO_TE_LONG) < VALEO_TE_DELTA) ||
(valeo_abs_diff(duration, VALEO_TE_LONG_ALT) < VALEO_TE_DELTA_ALT);
}
static bool renault_valeo_frame_is_plausible(RenaultValeoDecoder* inst) {
if(inst->bit_count < VALEO_MIN_BITS || inst->bit_count > VALEO_MAX_BITS) {
return false;
}
if((inst->bit_count % 2U) != 0U) {
return false;
}
const uint32_t fix = (uint32_t)(inst->data >> 32U);
const uint8_t btn = (uint8_t)((fix >> 28U) & 0x0FU);
const uint32_t serial = fix & 0x0FFFFFFFU;
return (serial != 0U) && (btn <= 0x0DU);
}
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
static void renault_valeo_decode_keeloq(RenaultValeoDecoder* inst) {
if(!inst->keystore) return;
uint32_t fix = (uint32_t)(inst->data >> 32);
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
uint8_t btn = (fix >> 28) & 0xF;
uint32_t serial = fix & 0x0FFFFFFF;
inst->generic.serial = serial;
inst->generic.btn = btn;
inst->manufacture_name = "Unknown";
for
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
// Normal Learning (Valeo primary)
if(mf->type == KEELOQ_LEARNING_NORMAL ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
// Simple Learning fallback
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_valeo_alloc(SubGhzEnvironment* env) {
RenaultValeoDecoder* inst = malloc(sizeof(RenaultValeoDecoder));
memset(inst, 0, sizeof(RenaultValeoDecoder));
inst->base.protocol = &subghz_protocol_renault_valeo;
inst->generic.protocol_name = inst->base.protocol->name;
inst->keystore = subghz_environment_get_keystore(env);
inst->manufacture_name = "Unknown";
return inst;
}
static void renault_valeo_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_valeo_reset(void* ctx) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
// ─── Feed — OOK PWM ─────────────────────────────────────────────────────────
static void renault_valeo_try_accept(RenaultValeoDecoder* inst) {
if(renault_valeo_frame_is_plausible(inst)) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_valeo_decode_keeloq(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
static void renault_valeo_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
switch(inst->parser_step) {
case ValeoStepReset:
if(level) {
if(renault_valeo_is_short(duration)) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else if(renault_valeo_is_long(duration)) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
}
}
break;
case ValeoStepWaitSpace:
if(!level) {
if(renault_valeo_is_short(duration)) {
inst->parser_step = ValeoStepWaitMark;
} else if(duration >= VALEO_GAP_MIN) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
} else {
// Allow some tolerance on space — accept wider spaces as inter-bit
if(duration < VALEO_GAP_MIN) {
inst->parser_step = ValeoStepWaitMark;
} else {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
}
break;
case ValeoStepWaitMark:
if(level) {
if(renault_valeo_is_short(duration)) {
inst->data = (inst->data << 1);
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else if(renault_valeo_is_long(duration)) {
inst->data = (inst->data << 1) | 1;
inst->bit_count++;
inst->parser_step = ValeoStepWaitSpace;
} else {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
} else {
if(duration >= VALEO_GAP_MIN) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
break;
default:
renault_valeo_reset(ctx);
break;
}
if(inst->bit_count > VALEO_MAX_BITS) {
renault_valeo_try_accept(inst);
inst->data = 0;
inst->bit_count = 0;
inst->parser_step = ValeoStepReset;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_valeo_get_hash(void* ctx) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_valeo_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
if(res == SubGhzProtocolStatusOk) {
if(!flipper_format_write_string_cstr(
flipper_format, "Manufacture", inst->manufacture_name)) {
res = SubGhzProtocolStatusErrorParserOthers;
}
}
return res;
}
static SubGhzProtocolStatus
renault_valeo_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, VALEO_MIN_BITS);
if(res == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
// Read manufacture name safely
FuriString* mf = furi_string_alloc();
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
// Store a copy since mf will be freed
if(furi_string_size(mf) > 0) {
inst->manufacture_name = "Loaded";
}
}
furi_string_free(mf);
// Re-extract fields
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
}
return res;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_valeo_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultValeoDecoder* inst = ctx;
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX Mf:%s\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt,
inst->manufacture_name);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
static const SubGhzProtocolDecoder renault_valeo_decoder = {
.alloc = renault_valeo_alloc,
.free = renault_valeo_free,
.feed = renault_valeo_feed,
.reset = renault_valeo_reset,
.get_hash_data = renault_valeo_get_hash,
.serialize = renault_valeo_serialize,
.deserialize = renault_valeo_deserialize,
.get_string = renault_valeo_get_string,
};
const SubGhzProtocol subghz_protocol_renault_valeo = {
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_valeo_decoder,
.encoder = NULL,
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME "Renault_Valeo"
extern const SubGhzProtocol subghz_protocol_renault_valeo;
+333
View File
@@ -0,0 +1,333 @@
#include "renault_valeo_fsk.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/generic.h"
#include "keeloq_common.h"
#include "../subghz_keystore.h"
#include "../subghz_keystore_i.h"
#include <lib/toolbox/manchester_decoder.h>
#include <m-array.h>
#define TAG "RenaultValeoFSK"
// Valeo FSK (Megane III, Scenic III, Ren3) — 2FSKDev476Async
// Manchester encoding over FSK
// te_short = 500 µs (half-bit cell)
// te_long = 1000 µs (full-bit cell)
// te_delta = 200 µs
// Preamble: alternating half-cells (min 8)
#define VALEO_FSK_TE_SHORT 500
#define VALEO_FSK_TE_LONG 1000
#define VALEO_FSK_TE_DELTA 200
#define VALEO_FSK_TE_SHORT_ALT 400
#define VALEO_FSK_TE_LONG_ALT 800
#define VALEO_FSK_TE_DELTA_ALT 180
#define VALEO_FSK_MIN_BITS 64
#define VALEO_FSK_MAX_BITS 96
#define VALEO_FSK_PREAMBLE_MIN 8
#ifndef DURATION_DIFF
#define DURATION_DIFF(x, y) (((x) > (y)) ? ((x) - (y)) : ((y) - (x)))
#endif
typedef enum {
ValeoFSKStepReset = 0,
ValeoFSKStepPreamble,
ValeoFSKStepDecode,
} ValeoFSKStep;
// ─── Struct ──────────────────────────────────────────────────────────────────
typedef struct {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint64_t data;
uint8_t bit_count;
uint8_t preamble_count;
ManchesterState manchester_state;
SubGhzKeystore* keystore;
const char* manufacture_name;
} RenaultValeoFSKDecoder;
static bool renault_valeo_fsk_frame_is_plausible(RenaultValeoFSKDecoder* inst) {
if(inst->bit_count < VALEO_FSK_MIN_BITS || inst->bit_count > VALEO_FSK_MAX_BITS) {
return false;
}
if((inst->bit_count % 2U) != 0U) {
return false;
}
const uint32_t fix = (uint32_t)(inst->data >> 32U);
const uint8_t btn = (uint8_t)((fix >> 28U) & 0x0FU);
const uint32_t serial = fix & 0x0FFFFFFFU;
return (serial != 0U) && (btn <= 0x0DU);
}
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
static void renault_valeo_fsk_decode_keeloq(RenaultValeoFSKDecoder* inst) {
if(!inst->keystore) return;
uint32_t fix = (uint32_t)(inst->data >> 32);
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
uint8_t btn = (fix >> 28) & 0xF;
uint32_t serial = fix & 0x0FFFFFFF;
inst->generic.serial = serial;
inst->generic.btn = btn;
inst->manufacture_name = "Unknown";
for
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
if(mf->type == KEELOQ_LEARNING_NORMAL ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
if((decrypt >> 28) == btn &&
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
inst->generic.cnt = decrypt & 0xFFFF;
inst->manufacture_name = furi_string_get_cstr(mf->name);
return;
}
}
}
}
// ─── Accept helper ───────────────────────────────────────────────────────────
static void renault_valeo_fsk_try_accept(RenaultValeoFSKDecoder* inst) {
if(renault_valeo_fsk_frame_is_plausible(inst)) {
inst->generic.data = inst->data;
inst->generic.data_count_bit = inst->bit_count;
renault_valeo_fsk_decode_keeloq(inst);
if(inst->base.callback) {
inst->base.callback(&inst->base, inst->base.context);
}
}
}
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
static void* renault_valeo_fsk_alloc(SubGhzEnvironment* env) {
RenaultValeoFSKDecoder* inst = malloc(sizeof(RenaultValeoFSKDecoder));
memset(inst, 0, sizeof(RenaultValeoFSKDecoder));
inst->base.protocol = &subghz_protocol_renault_valeo_fsk;
inst->generic.protocol_name = inst->base.protocol->name;
inst->keystore = subghz_environment_get_keystore(env);
inst->manufacture_name = "Unknown";
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepReset;
return inst;
}
static void renault_valeo_fsk_free(void* ctx) {
furi_assert(ctx);
free(ctx);
}
static void renault_valeo_fsk_reset(void* ctx) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
inst->data = 0;
inst->bit_count = 0;
inst->preamble_count = 0;
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepReset;
}
// ─── Feed — Manchester over FSK ──────────────────────────────────────────────
static void renault_valeo_fsk_feed(void* ctx, bool level, uint32_t duration) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
// Classify duration
ManchesterEvent event = ManchesterEventReset;
if((DURATION_DIFF(duration, VALEO_FSK_TE_SHORT) < VALEO_FSK_TE_DELTA) ||
(DURATION_DIFF(duration, VALEO_FSK_TE_SHORT_ALT) < VALEO_FSK_TE_DELTA_ALT)) {
event = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
} else if((DURATION_DIFF(duration, VALEO_FSK_TE_LONG) < VALEO_FSK_TE_DELTA) ||
(DURATION_DIFF(duration, VALEO_FSK_TE_LONG_ALT) < VALEO_FSK_TE_DELTA_ALT)) {
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
} else {
// Out of range — gap or noise
renault_valeo_fsk_try_accept(inst);
renault_valeo_fsk_reset(ctx);
return;
}
switch(inst->decoder.parser_step) {
case ValeoFSKStepReset:
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
inst->preamble_count = 1;
inst->decoder.parser_step = ValeoFSKStepPreamble;
}
break;
case ValeoFSKStepPreamble:
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
inst->preamble_count++;
if(inst->preamble_count >= VALEO_FSK_PREAMBLE_MIN) {
inst->data = 0;
inst->bit_count = 0;
inst->manchester_state = ManchesterStateMid1;
inst->decoder.parser_step = ValeoFSKStepDecode;
}
} else {
renault_valeo_fsk_reset(ctx);
}
break;
case ValeoFSKStepDecode: {
bool bit_out = false;
ManchesterState next_state;
if(manchester_advance(
inst->manchester_state, event, &next_state, &bit_out)) {
inst->data = (inst->data << 1) | (bit_out ? 1 : 0);
inst->bit_count++;
if(inst->bit_count >= VALEO_FSK_MAX_BITS) {
renault_valeo_fsk_try_accept(inst);
renault_valeo_fsk_reset(ctx);
return;
}
}
inst->manchester_state = next_state;
break;
}
default:
renault_valeo_fsk_reset(ctx);
break;
}
}
// ─── Hash ────────────────────────────────────────────────────────────────────
static uint8_t renault_valeo_fsk_get_hash(void* ctx) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
return (uint8_t)(inst->generic.data ^
(inst->generic.data >> 8) ^
(inst->generic.data >> 16) ^
(inst->generic.data >> 24));
}
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
static SubGhzProtocolStatus renault_valeo_fsk_serialize(
void* ctx,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
if(res == SubGhzProtocolStatusOk) {
if(!flipper_format_write_string_cstr(
flipper_format, "Manufacture", inst->manufacture_name)) {
res = SubGhzProtocolStatusErrorParserOthers;
}
}
return res;
}
static SubGhzProtocolStatus
renault_valeo_fsk_deserialize(void* ctx, FlipperFormat* flipper_format) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
SubGhzProtocolStatus res =
subghz_block_generic_deserialize_check_count_bit(
&inst->generic, flipper_format, VALEO_FSK_MIN_BITS);
if(res == SubGhzProtocolStatusOk) {
inst->data = inst->generic.data;
inst->bit_count = inst->generic.data_count_bit;
FuriString* mf = furi_string_alloc();
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
if(furi_string_size(mf) > 0) {
inst->manufacture_name = "Loaded";
}
}
furi_string_free(mf);
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
}
return res;
}
// ─── get_string ──────────────────────────────────────────────────────────────
static void renault_valeo_fsk_get_string(void* ctx, FuriString* output) {
furi_assert(ctx);
RenaultValeoFSKDecoder* inst = ctx;
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
inst->generic.serial = fix & 0x0FFFFFFF;
inst->generic.btn = (fix >> 28) & 0xF;
subghz_block_generic_global.btn_is_available = true;
subghz_block_generic_global.current_btn = inst->generic.btn;
subghz_block_generic_global.btn_length_bit = 4;
subghz_block_generic_global.cnt_is_available = true;
subghz_block_generic_global.current_cnt = inst->generic.cnt;
subghz_block_generic_global.cnt_length_bit = 16;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%016llX\r\n"
"Sn:%07lX Btn:%X\r\n"
"Cnt:%04lX Mf:%s\r\n",
inst->generic.protocol_name,
inst->generic.data_count_bit,
(unsigned long long)inst->generic.data,
(unsigned long)inst->generic.serial,
(unsigned int)inst->generic.btn,
(unsigned long)inst->generic.cnt,
inst->manufacture_name);
}
// ─── Descriptor ──────────────────────────────────────────────────────────────
static const SubGhzProtocolDecoder renault_valeo_fsk_decoder = {
.alloc = renault_valeo_fsk_alloc,
.free = renault_valeo_fsk_free,
.feed = renault_valeo_fsk_feed,
.reset = renault_valeo_fsk_reset,
.get_hash_data = renault_valeo_fsk_get_hash,
.serialize = renault_valeo_fsk_serialize,
.deserialize = renault_valeo_fsk_deserialize,
.get_string = renault_valeo_fsk_get_string,
};
const SubGhzProtocol subghz_protocol_renault_valeo_fsk = {
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 |
SubGhzProtocolFlag_FM |
SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load |
SubGhzProtocolFlag_Save,
.decoder = &renault_valeo_fsk_decoder,
.encoder = NULL,
};
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <lib/subghz/protocols/base.h>
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME "Renault_Valeo_FSK"
extern const SubGhzProtocol subghz_protocol_renault_valeo_fsk;