Files
Momentum-Firmware/lib/subghz/protocols/schrader_gg4.c
2024-03-14 01:12:32 +00:00

299 lines
10 KiB
C

#include "schrader_gg4.h"
#include <lib/toolbox/manchester_decoder.h>
#define TAG "Schrader"
// https://github.com/merbanan/rtl_433/blob/master/src/devices/schraeder.c
// https://elib.dlr.de/81155/1/TPMS_for_Trafffic_Management_purposes.pdf
// https://github.com/furrtek/portapack-havoc/issues/349
// https://fccid.io/MRXGG4
// https://fccid.io/MRXGG4T
/**
* Schrader 3013/3015 MRX-GG4
OEM:
KIA Sportage CGA 11-SPT1504-RA
Mercedes-Benz A0009054100
* Frequency: 433.92MHz+-38KHz
* Modulation: ASK
* Working Temperature: -50°C to 125°C
* Tire monitoring range value: 0kPa-350kPa+-7kPa
Examples in normal environmental conditions:
3000878456094cd0
3000878456084ecb
3000878456074d01
Data layout:
* | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
* | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
* | SSSS SSSS | IIII IIII | IIII IIII | IIII IIII | IIII IIII | PPPP PPPP | TTTT TTTT | CCCC CCCC |
*
- The preamble is 0b000
- S: always 0x30 in relearn state
- I: 32 bit ID
- P: 8 bit Pressure (multiplyed by 2.5 = PSI)
- T: 8 bit Temperature (deg. C offset by 50)
- C: 8 bit Checksum (CRC8, Poly 0x7, Init 0x0)
*/
#define PREAMBLE 0b000
#define PREAMBLE_BITS_LEN 3
static const SubGhzBlockConst tpms_protocol_schrader_gg4_const = {
.te_short = 120,
.te_long = 240,
.te_delta = 55, // 50% of te_short due to poor sensitivity
.min_count_bit_for_found = 64,
};
struct TPMSProtocolDecoderSchraderGG4 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
TPMSBlockGeneric generic;
ManchesterState manchester_saved_state;
uint16_t header_count;
};
struct TPMSProtocolEncoderSchraderGG4 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
TPMSBlockGeneric generic;
};
typedef enum {
SchraderGG4DecoderStepReset = 0,
SchraderGG4DecoderStepCheckPreamble,
SchraderGG4DecoderStepDecoderData,
SchraderGG4DecoderStepSaveDuration,
SchraderGG4DecoderStepCheckDuration,
} SchraderGG4DecoderStep;
const SubGhzProtocolDecoder tpms_protocol_schrader_gg4_decoder = {
.alloc = tpms_protocol_decoder_schrader_gg4_alloc,
.free = tpms_protocol_decoder_schrader_gg4_free,
.feed = tpms_protocol_decoder_schrader_gg4_feed,
.reset = tpms_protocol_decoder_schrader_gg4_reset,
.get_hash_data = NULL,
.get_hash_data_long = tpms_protocol_decoder_schrader_gg4_get_hash_data,
.serialize = tpms_protocol_decoder_schrader_gg4_serialize,
.deserialize = tpms_protocol_decoder_schrader_gg4_deserialize,
.get_string = tpms_protocol_decoder_schrader_gg4_get_string,
};
const SubGhzProtocolEncoder tpms_protocol_schrader_gg4_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol tpms_protocol_schrader_gg4 = {
.name = TPMS_PROTOCOL_SCHRADER_GG4_NAME,
.type = SubGhzProtocolTypeStatic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
.decoder = &tpms_protocol_schrader_gg4_decoder,
.encoder = &tpms_protocol_schrader_gg4_encoder,
.filter = SubGhzProtocolFilter_TPMS,
};
void* tpms_protocol_decoder_schrader_gg4_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
TPMSProtocolDecoderSchraderGG4* instance = malloc(sizeof(TPMSProtocolDecoderSchraderGG4));
instance->base.protocol = &tpms_protocol_schrader_gg4;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void tpms_protocol_decoder_schrader_gg4_free(void* context) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
free(instance);
}
void tpms_protocol_decoder_schrader_gg4_reset(void* context) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
instance->decoder.parser_step = SchraderGG4DecoderStepReset;
}
static bool tpms_protocol_schrader_gg4_check_crc(TPMSProtocolDecoderSchraderGG4* instance) {
uint8_t msg[] = {
instance->decoder.decode_data >> 48,
instance->decoder.decode_data >> 40,
instance->decoder.decode_data >> 32,
instance->decoder.decode_data >> 24,
instance->decoder.decode_data >> 16,
instance->decoder.decode_data >> 8};
uint8_t crc = subghz_protocol_blocks_crc8(msg, 6, 0x7, 0);
return (crc == (instance->decoder.decode_data & 0xFF));
}
/**
* Analysis of received data
* @param instance Pointer to a TPMSBlockGeneric* instance
*/
static void tpms_protocol_schrader_gg4_analyze(TPMSBlockGeneric* instance) {
instance->id = instance->data >> 24;
// TODO locate and fix
instance->battery_low = TPMS_NO_BATT;
instance->temperature = ((instance->data >> 8) & 0xFF) - 50;
instance->pressure = ((instance->data >> 16) & 0xFF) * 2.5 * 0.069;
}
static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
bool is_long = false;
if(DURATION_DIFF(duration, tpms_protocol_schrader_gg4_const.te_long) <
tpms_protocol_schrader_gg4_const.te_delta) {
is_long = true;
} else if(
DURATION_DIFF(duration, tpms_protocol_schrader_gg4_const.te_short) <
tpms_protocol_schrader_gg4_const.te_delta) {
is_long = false;
} else {
return ManchesterEventReset;
}
if(level)
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
else
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
}
void tpms_protocol_decoder_schrader_gg4_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
bool bit = false;
bool have_bit = false;
TPMSProtocolDecoderSchraderGG4* instance = context;
// low-level bit sequence decoding
if(instance->decoder.parser_step != SchraderGG4DecoderStepReset) {
ManchesterEvent event = level_and_duration_to_event(level, duration);
if(event == ManchesterEventReset) {
if((instance->decoder.parser_step == SchraderGG4DecoderStepDecoderData) &&
instance->decoder.decode_count_bit) {
// FURI_LOG_D(TAG, "%d-%ld", level, duration);
FURI_LOG_D(
TAG,
"reset accumulated %d bits: %llx",
instance->decoder.decode_count_bit,
instance->decoder.decode_data);
}
instance->decoder.parser_step = SchraderGG4DecoderStepReset;
} else {
have_bit = manchester_advance(
instance->manchester_saved_state, event, &instance->manchester_saved_state, &bit);
if(!have_bit) return;
// Invert value, due to signal is Manchester II and decoder is Manchester I
bit = !bit;
}
}
switch(instance->decoder.parser_step) {
case SchraderGG4DecoderStepReset:
// wait for start ~480us pulse
if((level) && (DURATION_DIFF(duration, tpms_protocol_schrader_gg4_const.te_long * 2) <
tpms_protocol_schrader_gg4_const.te_delta)) {
instance->decoder.parser_step = SchraderGG4DecoderStepCheckPreamble;
instance->header_count = 0;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
// First will be short space, so set correct initial state for machine
// https://clearwater.com.au/images/rc5/rc5-state-machine.gif
instance->manchester_saved_state = ManchesterStateStart1;
}
break;
case SchraderGG4DecoderStepCheckPreamble:
if(bit != 0) {
instance->decoder.parser_step = SchraderGG4DecoderStepReset;
break;
}
instance->header_count++;
if(instance->header_count == PREAMBLE_BITS_LEN)
instance->decoder.parser_step = SchraderGG4DecoderStepDecoderData;
break;
case SchraderGG4DecoderStepDecoderData:
subghz_protocol_blocks_add_bit(&instance->decoder, bit);
if(instance->decoder.decode_count_bit ==
tpms_protocol_schrader_gg4_const.min_count_bit_for_found) {
FURI_LOG_D(TAG, "%016llx", instance->decoder.decode_data);
if(!tpms_protocol_schrader_gg4_check_crc(instance)) {
FURI_LOG_D(TAG, "CRC mismatch drop");
} else {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
tpms_protocol_schrader_gg4_analyze(&instance->generic);
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.parser_step = SchraderGG4DecoderStepReset;
}
break;
}
}
uint32_t tpms_protocol_decoder_schrader_gg4_get_hash_data(void* context) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
return subghz_protocol_blocks_get_hash_data_long(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus tpms_protocol_decoder_schrader_gg4_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
return tpms_block_generic_serialize(&instance->generic, flipper_format, preset);
}
SubGhzProtocolStatus
tpms_protocol_decoder_schrader_gg4_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
return tpms_block_generic_deserialize_check_count_bit(
&instance->generic,
flipper_format,
tpms_protocol_schrader_gg4_const.min_count_bit_for_found);
}
void tpms_protocol_decoder_schrader_gg4_get_string(void* context, FuriString* output) {
furi_assert(context);
TPMSProtocolDecoderSchraderGG4* instance = context;
furi_string_cat_printf(
output,
"%s\r\n"
"Id:0x%08lX\r\n"
"Bat:%d\r\n"
"Temp:%2.0f C Bar:%2.1f",
instance->generic.protocol_name,
instance->generic.id,
instance->generic.battery_low,
(double)instance->generic.temperature,
(double)instance->generic.pressure);
}