Merge remote-tracking branch 'pp_net/zero-mega' into pp_main
FAP Build / ufbt: Build for Momentum release (push) Failing after 50s
FAP Build / ufbt: Build for OFW dev channel (push) Failing after 49s
FAP Build / ufbt: Build for OFW release channel (push) Failing after 51s
FAP Build / ufbt: Build for Unleashed dev (push) Failing after 52s
FAP Build / ufbt: Build for Unleashed release (push) Failing after 47s
FAP Build / ufbt: Build for Momentum dev (push) Failing after 50s

This commit is contained in:
MX
2026-04-15 22:28:32 +03:00
8 changed files with 1484 additions and 279 deletions
+728
View File
@@ -0,0 +1,728 @@
#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;
};
#ifdef ENABLE_EMULATE_FEATURE
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;
};
#endif
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);
}
#ifdef ENABLE_EMULATE_FEATURE
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 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 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;
}
#endif
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,
};
#ifdef ENABLE_EMULATE_FEATURE
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,
};
#else
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
#endif
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,
};
#ifdef ENABLE_EMULATE_FEATURE
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;
}
#endif
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;
}
+46
View File
@@ -0,0 +1,46 @@
#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>
#include "../defines.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);
#ifdef ENABLE_EMULATE_FEATURE
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);
#endif
+645 -268
View File
@@ -1,350 +1,727 @@
#include "mazda_v0.h"
#include <string.h>
// Original implementation by @lupettohf
#define MAZDA_PREAMBLE_MIN 13
#define MAZDA_COMPLETION_MIN 80
#define MAZDA_COMPLETION_MAX 105
#define MAZDA_DATA_BUFFER_SIZE 14
// =============================================================================
// PROTOCOL CONSTANTS
// =============================================================================
static const SubGhzBlockConst subghz_protocol_mazda_const = {
static const SubGhzBlockConst subghz_protocol_mazda_v0_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit_for_found = 64,
};
typedef enum {
MazdaDecoderStepReset = 0,
MazdaDecoderStepPreambleSave,
MazdaDecoderStepPreambleCheck,
MazdaDecoderStepDataSave,
MazdaDecoderStepDataCheck,
} MazdaDecoderStep;
#define MAZDA_V0_UPLOAD_CAPACITY 0x184
#define MAZDA_V0_GAP_US 0xCB20
#define MAZDA_V0_SYNC_BYTE 0xD7
#define MAZDA_V0_TAIL_BYTE 0x5A
#define MAZDA_V0_PREAMBLE_ONES 16
struct SubGhzProtocolDecoderMazda {
// =============================================================================
// STRUCT DEFINITIONS
// =============================================================================
typedef struct SubGhzProtocolDecoderMazdaV0 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
ManchesterState manchester_state;
uint16_t preamble_count;
uint16_t bit_counter;
uint8_t prev_state;
uint8_t data_buffer[MAZDA_DATA_BUFFER_SIZE];
uint8_t preamble_pattern;
uint32_t serial;
uint8_t button;
uint32_t count;
} SubGhzProtocolDecoderMazdaV0;
#ifdef ENABLE_EMULATE_FEATURE
typedef struct SubGhzProtocolEncoderMazdaV0 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
uint32_t serial;
uint8_t button;
uint32_t count;
} SubGhzProtocolEncoderMazdaV0;
#endif
typedef enum {
MazdaV0DecoderStepReset = 0,
MazdaV0DecoderStepPreamble = 5,
MazdaV0DecoderStepData = 6,
} MazdaV0DecoderStep;
// =============================================================================
// FUNCTION PROTOTYPES
// =============================================================================
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event);
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic);
#ifdef ENABLE_EMULATE_FEATURE
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter);
static bool mazda_v0_encoder_add_level(
SubGhzProtocolEncoderMazdaV0* instance,
size_t* index,
bool level,
uint32_t duration);
static bool mazda_v0_append_byte(
SubGhzProtocolEncoderMazdaV0* instance,
size_t* index,
uint8_t value);
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance);
#endif
static SubGhzProtocolStatus
mazda_v0_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button);
// =============================================================================
// PROTOCOL INTERFACE DEFINITIONS
// =============================================================================
const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
.alloc = subghz_protocol_decoder_mazda_v0_alloc,
.free = subghz_protocol_decoder_mazda_v0_free,
.feed = subghz_protocol_decoder_mazda_v0_feed,
.reset = subghz_protocol_decoder_mazda_v0_reset,
.get_hash_data = subghz_protocol_decoder_mazda_v0_get_hash_data,
.serialize = subghz_protocol_decoder_mazda_v0_serialize,
.deserialize = subghz_protocol_decoder_mazda_v0_deserialize,
.get_string = subghz_protocol_decoder_mazda_v0_get_string,
};
// ============================================================================
// Helpers
// ============================================================================
static uint8_t mazda_byte_parity(uint8_t value) {
value ^= value >> 4;
value ^= value >> 2;
value ^= value >> 1;
return value & 1;
}
static void mazda_xor_deobfuscate(uint8_t* data) {
uint8_t parity = mazda_byte_parity(data[7]);
if(parity) {
uint8_t mask = data[6];
for(uint8_t i = 0; i < 6; i++) {
data[i] ^= mask;
}
} else {
uint8_t mask = data[5];
for(uint8_t i = 0; i < 5; i++) {
data[i] ^= mask;
}
data[6] ^= mask;
}
uint8_t old5 = data[5];
uint8_t old6 = data[6];
data[5] = (old5 & 0xAAU) | (old6 & 0x55U);
data[6] = (old5 & 0x55U) | (old6 & 0xAAU);
}
static void mazda_parse_data(SubGhzBlockGeneric* generic) {
generic->serial = (uint32_t)(generic->data >> 32);
generic->btn = (generic->data >> 24) & 0xFF;
generic->cnt = (generic->data >> 8) & 0xFFFF;
}
static const char* mazda_get_btn_name(uint8_t btn) {
switch(btn) {
case 0x10:
return "Lock";
case 0x20:
return "Unlock";
case 0x40:
return "Trunk";
default:
return "Unknown";
}
}
static inline bool mazda_is_short(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_mazda_const.te_short) <
subghz_protocol_mazda_const.te_delta;
}
static inline bool mazda_is_long(uint32_t duration) {
return DURATION_DIFF(duration, subghz_protocol_mazda_const.te_long) <
subghz_protocol_mazda_const.te_delta;
}
static void mazda_collect_bit(SubGhzProtocolDecoderMazda* instance, uint8_t state_bit) {
uint8_t byte_idx = instance->bit_counter >> 3;
if(byte_idx < MAZDA_DATA_BUFFER_SIZE) {
instance->data_buffer[byte_idx] <<= 1;
if(state_bit == 0) {
instance->data_buffer[byte_idx] |= 1;
}
}
instance->bit_counter++;
}
static bool mazda_check_completion(SubGhzProtocolDecoderMazda* instance) {
if(instance->bit_counter < MAZDA_COMPLETION_MIN || instance->bit_counter > MAZDA_COMPLETION_MAX) {
return false;
}
// Shift buffer by 1 byte (discard sync/header byte)
uint8_t data[8];
for(uint8_t i = 0; i < 8; i++) {
data[i] = instance->data_buffer[i + 1];
}
mazda_xor_deobfuscate(data);
uint8_t checksum = 0;
for(uint8_t i = 0; i < 7; i++) {
checksum += data[i];
}
if(checksum != data[7]) {
return false;
}
uint64_t packed = 0;
for(uint8_t i = 0; i < 8; i++) {
packed = (packed << 8) | data[i];
}
instance->generic.data = packed;
instance->generic.data_count_bit = 64;
mazda_parse_data(&instance->generic);
return true;
}
static bool
mazda_process_pair(SubGhzProtocolDecoderMazda* instance, uint32_t dur_first, uint32_t dur_second) {
bool first_short = mazda_is_short(dur_first);
bool first_long = mazda_is_long(dur_first);
bool second_short = mazda_is_short(dur_second);
bool second_long = mazda_is_long(dur_second);
if(first_long && second_short) {
mazda_collect_bit(instance, 0);
mazda_collect_bit(instance, 1);
instance->prev_state = 1;
return true;
}
if(first_short && second_long) {
mazda_collect_bit(instance, 1);
instance->prev_state = 0;
return true;
}
if(first_short && second_short) {
mazda_collect_bit(instance, instance->prev_state);
return true;
}
if(first_long && second_long) {
mazda_collect_bit(instance, 0);
mazda_collect_bit(instance, 1);
instance->prev_state = 0;
return true;
}
return false;
}
const SubGhzProtocolDecoder subghz_protocol_mazda_decoder = {
.alloc = subghz_protocol_decoder_mazda_alloc,
.free = subghz_protocol_decoder_mazda_free,
.feed = subghz_protocol_decoder_mazda_feed,
.reset = subghz_protocol_decoder_mazda_reset,
.get_hash_data = subghz_protocol_decoder_mazda_get_hash_data,
.serialize = subghz_protocol_decoder_mazda_serialize,
.deserialize = subghz_protocol_decoder_mazda_deserialize,
.get_string = subghz_protocol_decoder_mazda_get_string,
#ifdef ENABLE_EMULATE_FEATURE
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
.alloc = subghz_protocol_encoder_mazda_v0_alloc,
.free = subghz_protocol_encoder_mazda_v0_free,
.deserialize = subghz_protocol_encoder_mazda_v0_deserialize,
.stop = subghz_protocol_encoder_mazda_v0_stop,
.yield = subghz_protocol_encoder_mazda_v0_yield,
};
const SubGhzProtocolEncoder subghz_protocol_mazda_encoder = {
#else
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
#endif
const SubGhzProtocol mazda_v0_protocol = {
.name = MAZDA_PROTOCOL_NAME,
.type = SubGhzProtocolTypeStatic,
.name = MAZDA_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.decoder = &subghz_protocol_mazda_decoder,
.encoder = &subghz_protocol_mazda_encoder,
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_mazda_v0_decoder,
.encoder = &subghz_protocol_mazda_v0_encoder,
};
// ============================================================================
// Decoder
// ============================================================================
// =============================================================================
// HELPERS
// =============================================================================
void* subghz_protocol_decoder_mazda_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderMazda* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazda));
static uint8_t mazda_v0_popcount8(uint8_t x) {
uint8_t count = 0;
while(x) {
count += x & 1;
x >>= 1;
}
return count;
}
static void mazda_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
for(size_t i = 0; i < 8; i++) {
bytes[i] = (uint8_t)((data >> ((7 - i) * 8)) & 0xFF);
}
}
static uint64_t mazda_v0_bytes_to_u64_be(const uint8_t bytes[8]) {
uint64_t data = 0;
for(size_t i = 0; i < 8; i++) {
data = (data << 8) | bytes[i];
}
return data;
}
static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) {
counter &= 0xFFFFFU;
return (uint8_t)(
((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) + ((serial >> 8) & 0xFF) +
(serial & 0xFF) + ((counter >> 8) & 0xFF) + (counter & 0xFF) +
((((counter >> 16) & 0x0F) | ((button & 0x0F) << 4)) & 0xFF));
}
static const char* mazda_v0_get_button_name(uint8_t button) {
switch(button) {
case 0x01:
return "Lock";
case 0x02:
return "Unlock";
case 0x04:
return "Trunk";
case 0x08:
return "Remote";
default:
return "??";
}
}
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event) {
const uint32_t tol = (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U;
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < tol) {
*event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
return true;
}
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_long) < tol) {
*event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
return true;
}
return false;
}
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
uint8_t data[8];
mazda_v0_u64_to_bytes_be(generic->data, data);
const bool parity = (mazda_v0_popcount8(data[7]) & 1) != 0;
const uint8_t limit = parity ? 6 : 5;
const uint8_t mask = data[limit];
for(uint8_t i = 0; i < limit; i++) {
data[i] ^= mask;
}
if(!parity) {
data[6] ^= mask;
}
const uint8_t counter_lo = (data[5] & 0x55) | (data[6] & 0xAA);
const uint8_t counter_mid = (data[6] & 0x55) | (data[5] & 0xAA);
generic->serial = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
((uint32_t)data[2] << 8) | (uint32_t)data[3];
generic->btn = (data[4] >> 4) & 0x0F;
generic->cnt = (((uint32_t)data[4] & 0x0F) << 16) | ((uint32_t)counter_mid << 8) |
(uint32_t)counter_lo;
generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
}
#ifdef ENABLE_EMULATE_FEATURE
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) {
uint8_t data[8];
counter &= 0xFFFFFU;
button &= 0x0F;
data[0] = (serial >> 24) & 0xFF;
data[1] = (serial >> 16) & 0xFF;
data[2] = (serial >> 8) & 0xFF;
data[3] = serial & 0xFF;
data[4] = (button << 4) | ((counter >> 16) & 0x0F);
data[5] = (counter >> 8) & 0xFF;
data[6] = counter & 0xFF;
data[7] = mazda_v0_calculate_checksum(serial, button, counter);
const uint8_t stored_5 = (data[6] & 0x55) | (data[5] & 0xAA);
const uint8_t stored_6 = (data[6] & 0xAA) | (data[5] & 0x55);
const uint8_t xor_mask = stored_5 ^ stored_6;
const bool replace_second = ((~mazda_v0_popcount8(data[7])) & 1) != 0;
const uint8_t forward_mask = replace_second ? stored_5 : stored_6;
data[5] = replace_second ? stored_5 : xor_mask;
data[6] = replace_second ? xor_mask : stored_6;
for(size_t i = 0; i < 5; i++) {
data[i] ^= forward_mask;
}
return mazda_v0_bytes_to_u64_be(data);
}
static bool mazda_v0_encoder_add_level(
SubGhzProtocolEncoderMazdaV0* instance,
size_t* index,
bool level,
uint32_t duration) {
if(*index >= MAZDA_V0_UPLOAD_CAPACITY) {
return false;
}
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
return true;
}
static bool mazda_v0_append_byte(
SubGhzProtocolEncoderMazdaV0* instance,
size_t* index,
uint8_t value) {
if(*index + 16 > MAZDA_V0_UPLOAD_CAPACITY) {
return false;
}
const uint32_t te = subghz_protocol_mazda_v0_const.te_short;
for(int8_t bit = 7; bit >= 0; bit--) {
const bool bit_value = ((value >> bit) & 1) != 0;
if(!bit_value) {
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
return false;
}
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
return false;
}
} else {
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
return false;
}
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
return false;
}
}
}
return true;
}
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) {
furi_check(instance);
size_t index = 0;
const uint64_t key64 = instance->generic.data;
for(size_t r = 0; r < 12; r++) {
if(!mazda_v0_append_byte(instance, &index, 0xFF)) {
return false;
}
}
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
return false;
}
if(!mazda_v0_append_byte(instance, &index, 0xFF) ||
!mazda_v0_append_byte(instance, &index, 0xFF) ||
!mazda_v0_append_byte(instance, &index, MAZDA_V0_SYNC_BYTE)) {
return false;
}
for(int bi = 0; bi < 8; bi++) {
const uint8_t raw = (uint8_t)((key64 >> (56 - bi * 8)) & 0xFF);
const uint8_t air = (uint8_t)~raw;
if(!mazda_v0_append_byte(instance, &index, air)) {
return false;
}
}
if(!mazda_v0_append_byte(instance, &index, MAZDA_V0_TAIL_BYTE)) {
return false;
}
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
return false;
}
instance->encoder.front = 0;
instance->encoder.size_upload = index;
return true;
}
#endif
static SubGhzProtocolStatus
mazda_v0_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) {
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
FuriString* display = furi_string_alloc();
furi_string_printf(
display, "%s - %s", protocol_name, mazda_v0_get_button_name(button));
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
status = SubGhzProtocolStatusErrorParserOthers;
}
furi_string_free(display);
return status;
}
// =============================================================================
// ENCODER
// =============================================================================
#ifdef ENABLE_EMULATE_FEATURE
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolEncoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMazdaV0));
furi_check(instance);
instance->base.protocol = &mazda_v0_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
instance->encoder.repeat = 10;
instance->encoder.size_upload = 0;
instance->encoder.front = 0;
instance->encoder.is_running = false;
instance->encoder.upload = malloc(MAZDA_V0_UPLOAD_CAPACITY * sizeof(LevelDuration));
furi_check(instance->encoder.upload);
return instance;
}
void subghz_protocol_decoder_mazda_free(void* context) {
void subghz_protocol_encoder_mazda_v0_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
SubGhzProtocolEncoderMazdaV0* instance = context;
free(instance->encoder.upload);
free(instance);
}
void subghz_protocol_decoder_mazda_reset(void* context) {
SubGhzProtocolStatus
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
instance->decoder.parser_step = MazdaDecoderStepReset;
instance->preamble_count = 0;
instance->bit_counter = 0;
instance->prev_state = 0;
instance->generic.data = 0;
instance->generic.data_count_bit = 0;
memset(instance->data_buffer, 0, sizeof(instance->data_buffer));
SubGhzProtocolEncoderMazdaV0* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
instance->encoder.is_running = false;
instance->encoder.front = 0;
instance->encoder.repeat = 10;
do {
FuriString* temp_str = furi_string_alloc();
if(!temp_str) {
break;
}
flipper_format_rewind(flipper_format);
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
furi_string_free(temp_str);
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
furi_string_free(temp_str);
break;
}
furi_string_free(temp_str);
flipper_format_rewind(flipper_format);
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
if(load_st != SubGhzProtocolStatusOk) {
break;
}
mazda_v0_decode_key(&instance->generic);
uint32_t u32 = 0;
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) {
instance->generic.serial = u32;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
instance->generic.btn = (uint8_t)u32;
}
flipper_format_rewind(flipper_format);
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
instance->generic.cnt = u32;
}
instance->serial = instance->generic.serial;
instance->button = instance->generic.btn;
instance->count = instance->generic.cnt;
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 10;
}
instance->generic.btn &= 0x0FU;
instance->generic.cnt &= 0xFFFFFU;
instance->generic.data = mazda_v0_encode_key(
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
instance->generic.data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
instance->serial = instance->generic.serial;
instance->button = instance->generic.btn;
instance->count = instance->generic.cnt;
if(!mazda_v0_build_upload(instance)) {
break;
}
if(instance->encoder.size_upload == 0) {
break;
}
flipper_format_rewind(flipper_format);
uint8_t key_data[sizeof(uint64_t)];
mazda_v0_u64_to_bytes_be(instance->generic.data, key_data);
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
break;
}
uint32_t chk = mazda_v0_calculate_checksum(
instance->serial, instance->button, instance->count);
flipper_format_rewind(flipper_format);
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &chk, 1);
instance->encoder.is_running = true;
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_decoder_mazda_feed(void* context, bool level, uint32_t duration) {
void subghz_protocol_encoder_mazda_v0_stop(void* context) {
furi_check(context);
UNUSED(level);
SubGhzProtocolDecoderMazda* instance = context;
SubGhzProtocolEncoderMazdaV0* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context) {
furi_check(context);
SubGhzProtocolEncoderMazdaV0* instance = context;
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration out = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return out;
}
#endif
// =============================================================================
// DECODER
// =============================================================================
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0));
furi_check(instance);
instance->base.protocol = &mazda_v0_protocol;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_mazda_v0_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
free(instance);
}
void subghz_protocol_decoder_mazda_v0_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
instance->decoder.parser_step = MazdaV0DecoderStepReset;
instance->decoder.te_last = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->manchester_state = ManchesterStateStart1;
instance->preamble_count = 0;
instance->preamble_pattern = 0;
}
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
ManchesterEvent event = ManchesterEventReset;
bool data = false;
switch(instance->decoder.parser_step) {
case MazdaDecoderStepReset:
if(mazda_is_short(duration)) {
instance->decoder.te_last = duration;
case MazdaV0DecoderStepReset:
if(level &&
((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) <
(uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U)) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = MazdaV0DecoderStepPreamble;
instance->manchester_state = ManchesterStateMid1;
instance->preamble_count = 0;
instance->decoder.parser_step = MazdaDecoderStepPreambleCheck;
instance->preamble_pattern = 0;
}
break;
case MazdaDecoderStepPreambleSave:
instance->decoder.te_last = duration;
instance->decoder.parser_step = MazdaDecoderStepPreambleCheck;
break;
case MazdaDecoderStepPreambleCheck:
if(mazda_is_short(instance->decoder.te_last) && mazda_is_short(duration)) {
instance->preamble_count++;
instance->decoder.parser_step = MazdaDecoderStepPreambleSave;
} else if(
mazda_is_short(instance->decoder.te_last) && mazda_is_long(duration) &&
instance->preamble_count >= MAZDA_PREAMBLE_MIN) {
instance->bit_counter = 1;
memset(instance->data_buffer, 0, sizeof(instance->data_buffer));
mazda_collect_bit(instance, 1);
instance->prev_state = 0;
instance->decoder.parser_step = MazdaDecoderStepDataSave;
} else {
instance->decoder.parser_step = MazdaDecoderStepReset;
case MazdaV0DecoderStepPreamble:
if(!mazda_v0_get_event(duration, level, &event)) {
instance->decoder.parser_step = MazdaV0DecoderStepReset;
break;
}
break;
case MazdaDecoderStepDataSave:
instance->decoder.te_last = duration;
instance->decoder.parser_step = MazdaDecoderStepDataCheck;
break;
if(manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data)) {
instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0);
case MazdaDecoderStepDataCheck:
if(mazda_process_pair(instance, instance->decoder.te_last, duration)) {
instance->decoder.parser_step = MazdaDecoderStepDataSave;
} else {
if(mazda_check_completion(instance) && instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
if(data) {
instance->preamble_count++;
} else if(instance->preamble_count <= MAZDA_V0_PREAMBLE_ONES - 1U) {
instance->preamble_count = 0;
instance->preamble_pattern = 0;
break;
}
if((instance->preamble_pattern == MAZDA_V0_SYNC_BYTE) &&
(instance->preamble_count > MAZDA_V0_PREAMBLE_ONES - 1U)) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = MazdaV0DecoderStepData;
}
}
break;
case MazdaV0DecoderStepData:
if(!mazda_v0_get_event(duration, level, &event)) {
instance->decoder.parser_step = MazdaV0DecoderStepReset;
break;
}
if(manchester_advance(
instance->manchester_state, event, &instance->manchester_state, &data)) {
subghz_protocol_blocks_add_bit(&instance->decoder, data);
if(instance->decoder.decode_count_bit ==
subghz_protocol_mazda_v0_const.min_count_bit_for_found) {
instance->generic.data = ~instance->decoder.decode_data;
mazda_v0_decode_key(&instance->generic);
if(mazda_v0_calculate_checksum(
instance->generic.serial, instance->generic.btn, instance->generic.cnt) ==
(uint8_t)instance->generic.data) {
instance->serial = instance->generic.serial;
instance->button = instance->generic.btn;
instance->count = instance->generic.cnt;
if(instance->base.callback) {
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->preamble_count = 0;
instance->preamble_pattern = 0;
instance->manchester_state = ManchesterStateStart1;
instance->decoder.te_last = 0;
instance->decoder.parser_step = MazdaV0DecoderStepReset;
}
instance->decoder.parser_step = MazdaDecoderStepReset;
}
break;
}
}
uint8_t subghz_protocol_decoder_mazda_get_hash_data(void* context) {
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
SubGhzBlockDecoder decoder = {
.decode_data = instance->generic.data,
.decode_count_bit = instance->generic.data_count_bit,
};
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
SubGhzProtocolDecoderMazdaV0* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_mazda_serialize(
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolDecoderMazdaV0* instance = context;
mazda_v0_decode_key(&instance->generic);
instance->serial = instance->generic.serial;
instance->button = instance->generic.btn;
instance->count = instance->generic.cnt;
SubGhzProtocolStatus ret =
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
SubGhzProtocolStatus
subghz_protocol_decoder_mazda_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_mazda_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
mazda_parse_data(&instance->generic);
uint32_t chk = mazda_v0_calculate_checksum(
instance->serial, instance->button, instance->count);
flipper_format_write_uint32(flipper_format, "Checksum", &chk, 1);
flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1);
uint32_t temp = instance->button;
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
flipper_format_write_uint32(flipper_format, "Cnt", &instance->count, 1);
ret = mazda_v0_write_display(
flipper_format, instance->generic.protocol_name, instance->button);
}
return ret;
}
void subghz_protocol_decoder_mazda_get_string(void* context, FuriString* output) {
SubGhzProtocolStatus
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderMazda* instance = context;
mazda_parse_data(&instance->generic);
SubGhzProtocolDecoderMazdaV0* instance = context;
uint8_t data[8];
for(uint8_t i = 0; i < 8; i++) {
data[i] = (instance->generic.data >> (56 - 8 * i)) & 0xFF;
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
if(ret == SubGhzProtocolStatusOk) {
flipper_format_rewind(flipper_format);
flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1);
instance->generic.serial = instance->serial;
uint32_t btn_temp = 0;
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
instance->button = (uint8_t)btn_temp;
instance->generic.btn = instance->button;
flipper_format_read_uint32(flipper_format, "Cnt", &instance->count, 1);
instance->generic.cnt = instance->count;
}
return ret;
}
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderMazdaV0* instance = context;
mazda_v0_decode_key(&instance->generic);
const uint8_t raw_crc = instance->generic.data & 0xFF;
const uint8_t calc_crc = mazda_v0_calculate_checksum(
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%02X %02X %02X %02X %02X %02X %02X %02X\r\n"
"Sn:%08lX Btn:%s\r\n"
"Cnt:%04lX Chk:%02X\r\n",
"%s %dbit CRC:%s\r\n"
"Key: %016llX\r\n"
"Sn: %08lX Btn: %02X - %s\r\n"
"Cnt: %05lX Chk: %02X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
data[0],
data[1],
data[2],
data[3],
data[4],
data[5],
data[6],
data[7],
(uint32_t)instance->generic.serial,
mazda_get_btn_name(instance->generic.btn),
(uint32_t)instance->generic.cnt,
data[7]);
(raw_crc == calc_crc) ? "OK" : "BAD",
(unsigned long long)instance->generic.data,
(unsigned long)instance->generic.serial,
instance->generic.btn,
mazda_v0_get_button_name(instance->generic.btn),
(unsigned long)(instance->generic.cnt & 0xFFFFFU),
raw_crc);
}
+19 -11
View File
@@ -5,27 +5,35 @@
#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>
#include "../defines.h"
#define MAZDA_PROTOCOL_NAME "Mazda V0"
typedef struct SubGhzProtocolDecoderMazda SubGhzProtocolDecoderMazda;
#define MAZDA_PROTOCOL_V0_NAME "Mazda V0"
extern const SubGhzProtocol mazda_v0_protocol;
void* subghz_protocol_decoder_mazda_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_mazda_free(void* context);
void subghz_protocol_decoder_mazda_reset(void* context);
void subghz_protocol_decoder_mazda_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_mazda_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_mazda_serialize(
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_mazda_v0_free(void* context);
void subghz_protocol_decoder_mazda_v0_reset(void* context);
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_mazda_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_mazda_get_string(void* context, FuriString* output);
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output);
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_mazda_v0_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_mazda_v0_stop(void* context);
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context);
+13
View File
@@ -13,6 +13,7 @@ const SubGhzProtocol* protopirate_protocol_registry_items[] = {
&fiat_protocol_v0, // Heap: free 16864
&fiat_v1_protocol,
&mazda_v0_protocol,
&kia_protocol_v7,
&mitsubishi_v0_protocol,
&porsche_touareg_protocol,
&subaru_protocol, // Heap: free 17280
@@ -85,6 +86,14 @@ static const ProtoPirateProtocolTiming protocol_timings[] = {
.te_delta = 100,
.min_count_bit = 144,
},
// Kia V7: Manchester 250/500µs
{
.name = KIA_PROTOCOL_V7_NAME,
.te_short = 250,
.te_long = 500,
.te_delta = 100,
.min_count_bit = 64,
},
// Ford V0: Manchester 250/500µs
{
.name = "Ford V0",
@@ -222,6 +231,10 @@ const ProtoPirateProtocolTiming* protopirate_get_protocol_timing(const char* pro
strstr(protocol_timings[i].name, "V5") != NULL) {
return &protocol_timings[i];
}
if(strstr(protocol_name, "V7") != NULL &&
strstr(protocol_timings[i].name, "V7") != NULL) {
return &protocol_timings[i];
}
}
// Match Ford
+1
View File
@@ -11,6 +11,7 @@
#include "kia_v3_v4.h"
#include "kia_v5.h"
#include "kia_v6.h"
#include "kia_v7.h"
#include "ford_v0.h"
#include "fiat_v0.h"
#include "fiat_v1.h"
+30
View File
@@ -102,6 +102,21 @@ static void emulate_context_free(void) {
static uint8_t
protopirate_get_button_for_protocol(const char* protocol, InputKey key, uint8_t original) {
// Kia V7
if(strcmp(protocol, KIA_PROTOCOL_V7_NAME) == 0) {
switch(key) {
case InputKeyUp:
return 0x01; // Lock
case InputKeyOk:
return 0x02; // Unlock
case InputKeyDown:
return 0x03; // Trunk
case InputKeyRight:
return 0x08; // Panic
default:
return original;
}
}
// Kia/Hyundai (all versions)
if(strstr(protocol, "Kia")) {
switch(key) {
@@ -165,6 +180,21 @@ static uint8_t
return original;
}
}
// Mazda V0
else if(strstr(protocol, "Mazda")) {
switch(key) {
case InputKeyUp:
return 0x01; // Lock
case InputKeyOk:
return 0x02; // Unlock
case InputKeyDown:
return 0x04; // Trunk
case InputKeyRight:
return 0x08; // Remote
default:
return original;
}
}
// Ford - (needs testing)
else if(strstr(protocol, "Ford")) {
switch(key) {
+2
View File
@@ -2,6 +2,8 @@
#include "../protopirate_app_i.h"
#include "proto_pirate_icons.h"
#define TAG "ProtoPirateNeedSaving"
static void
protopirate_scene_need_saving_callback(GuiButtonType result, InputType type, void* context) {
furi_assert(context);