diff --git a/applications/main/application.fam b/applications/main/application.fam index 5f5c9f97de8..761a64fe6fa 100644 --- a/applications/main/application.fam +++ b/applications/main/application.fam @@ -29,6 +29,7 @@ App( "ibutton_start", "onewire_start", "subghz_start", + "subghz_load_extended_settings", "infrared_start", "lfrfid_start", "nfc_start", diff --git a/lib/subghz/blocks/decoder.c b/lib/subghz/blocks/decoder.c index f491c87bfb5..83dcf5435f0 100644 --- a/lib/subghz/blocks/decoder.c +++ b/lib/subghz/blocks/decoder.c @@ -25,3 +25,15 @@ uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t } return hash; } + +uint32_t subghz_protocol_blocks_get_hash_data_long(SubGhzBlockDecoder* decoder, size_t len) { + union { + uint32_t full; + uint8_t split[4]; + } hash = {0}; + uint8_t* p = (uint8_t*)&decoder->decode_data; + for(size_t i = 0; i < len; i++) { + hash.split[i % sizeof(hash)] ^= p[i]; + } + return hash.full; +} diff --git a/lib/subghz/blocks/decoder.h b/lib/subghz/blocks/decoder.h index a5e561e3517..7fe150b5cfb 100644 --- a/lib/subghz/blocks/decoder.h +++ b/lib/subghz/blocks/decoder.h @@ -42,6 +42,13 @@ void subghz_protocol_blocks_add_to_128_bit( */ uint8_t subghz_protocol_blocks_get_hash_data(SubGhzBlockDecoder* decoder, size_t len); +/** + * Getting the long hash sum of the last randomly received parcel. + * @param decoder Pointer to a SubGhzBlockDecoder instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_blocks_get_hash_data_long(SubGhzBlockDecoder* decoder, size_t len); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/lib/subghz/blocks/generic.c b/lib/subghz/blocks/generic.c index 25357638f41..8663c32ca73 100644 --- a/lib/subghz/blocks/generic.c +++ b/lib/subghz/blocks/generic.c @@ -66,6 +66,16 @@ SubGhzProtocolStatus subghz_block_generic_serialize( break; } } + if(!flipper_format_write_float(flipper_format, "Latitute", &preset->latitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Latitute"); + res = SubGhzProtocolStatusErrorParserLatitude; + break; + } + if(!flipper_format_write_float(flipper_format, "Longitude", &preset->longitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Longitude"); + res = SubGhzProtocolStatusErrorParserLongitude; + break; + } if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { FURI_LOG_E(TAG, "Unable to add Protocol"); res = SubGhzProtocolStatusErrorParserProtocolName; diff --git a/lib/subghz/blocks/generic.h b/lib/subghz/blocks/generic.h index 10e7b63fabe..7d43589171e 100644 --- a/lib/subghz/blocks/generic.h +++ b/lib/subghz/blocks/generic.h @@ -17,6 +17,8 @@ typedef struct SubGhzBlockGeneric SubGhzBlockGeneric; struct SubGhzBlockGeneric { const char* protocol_name; + float latitude; + float longitude; uint64_t data; uint64_t data_2; uint32_t serial; diff --git a/lib/subghz/devices/types.h b/lib/subghz/devices/types.h index f5f140c6a75..01df1594004 100644 --- a/lib/subghz/devices/types.h +++ b/lib/subghz/devices/types.h @@ -95,4 +95,4 @@ struct SubGhzDeviceConf { uint8_t ver; bool extended_range; bool power_amp; -}; \ No newline at end of file +}; diff --git a/lib/subghz/protocols/acurite_592txr.c b/lib/subghz/protocols/acurite_592txr.c new file mode 100644 index 00000000000..f5b4ecee332 --- /dev/null +++ b/lib/subghz/protocols/acurite_592txr.c @@ -0,0 +1,420 @@ +#include "acurite_592txr.h" + +#define TAG "WSProtocolAcurite_592TXR" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c + * + * Acurite 592TXR Temperature Humidity sensor decoder + * Message Type 0x04, 7 bytes + * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | + * | --------- | --------- | --------- | --------- | --------- | --------- | --------- | + * | CCII IIII | IIII IIII | pB00 0100 | pHHH HHHH | p??T TTTT | pTTT TTTT | KKKK KKKK | + * - C: Channel 00: C, 10: B, 11: A, (01 is invalid) + * - I: Device ID (14 bits) + * - B: Battery, 1 is battery OK, 0 is battery low + * - M: Message type (6 bits), 0x04 + * - T: Temperature Celsius (11 - 14 bits?), + 1000 * 10 + * - H: Relative Humidity (%) (7 bits) + * - K: Checksum (8 bits) + * - p: Parity bit + * Notes: + * - Temperature + * - Encoded as Celsius + 1000 * 10 + * - only 11 bits needed for specified range -40 C to 70 C (-40 F - 158 F) + * - However 14 bits available for temperature, giving possible range of -100 C to 1538.4 C + * - @todo - check if high 3 bits ever used for anything else + * + * SUB file generator: https://htotoo.github.io/FlipperSUBGenerator/acurite_592txr-generator/index.html + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_592txr_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 90, + .min_count_bit_for_found = 56, +}; + +struct WSProtocolDecoderAcurite_592TXR { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderAcurite_592TXR { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_592TXRDecoderStepReset = 0, + Acurite_592TXRDecoderStepCheckPreambule, + Acurite_592TXRDecoderStepSaveDuration, + Acurite_592TXRDecoderStepCheckDuration, +} Acurite_592TXRDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder = { + .alloc = ws_protocol_decoder_acurite_592txr_alloc, + .free = ws_protocol_decoder_acurite_592txr_free, + + .feed = ws_protocol_decoder_acurite_592txr_feed, + .reset = ws_protocol_decoder_acurite_592txr_reset, + + .get_hash_data = ws_protocol_decoder_acurite_592txr_get_hash_data, + .serialize = ws_protocol_decoder_acurite_592txr_serialize, + .deserialize = ws_protocol_decoder_acurite_592txr_deserialize, + .get_string = ws_protocol_decoder_acurite_592txr_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder = { + .alloc = ws_protocol_encoder_acurite_592txr_alloc, + .free = ws_protocol_encoder_acurite_592txr_free, + + .deserialize = ws_protocol_encoder_acurite_592txr_deserialize, + .stop = ws_protocol_encoder_acurite_592txr_stop, + .yield = ws_protocol_encoder_acurite_592txr_yield, +}; + +const SubGhzProtocol ws_protocol_acurite_592txr = { + .name = WS_PROTOCOL_ACURITE_592TXR_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &ws_protocol_acurite_592txr_decoder, + .encoder = &ws_protocol_acurite_592txr_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolDecoderAcurite_592TXR)); + instance->base.protocol = &ws_protocol_acurite_592txr; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_592txr_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_592txr_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; +} + +static bool ws_protocol_acurite_592txr_check_crc(WSProtocolDecoderAcurite_592TXR* 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}; + + if((subghz_protocol_blocks_add_bytes(msg, 6) == + (uint8_t)(instance->decoder.decode_data & 0xFF)) && + (!subghz_protocol_blocks_parity_bytes(&msg[2], 4))) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_592txr_remote_controller(WSBlockGeneric* instance) { + uint8_t channel[] = {3, 0, 2, 1}; + uint8_t channel_raw = ((instance->data >> 54) & 0x03); + instance->channel = channel[channel_raw]; + instance->id = (instance->data >> 40) & 0x3FFF; + instance->battery_low = !((instance->data >> 38) & 1); + instance->humidity = (instance->data >> 24) & 0x7F; + + uint16_t temp_raw = ((instance->data >> 9) & 0xF80) | ((instance->data >> 8) & 0x7F); + instance->temp = ((float)(temp_raw)-1000) / 10.0f; + + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_592TXRDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case Acurite_592TXRDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short * 3) < + ws_protocol_acurite_592txr_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if((instance->header_count > 2) && (instance->header_count < 5)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } + break; + + case Acurite_592TXRDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_592TXRDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + + case Acurite_592TXRDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_acurite_592txr_const.te_short * 5)) { + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_592txr_const.min_count_bit_for_found) && + ws_protocol_acurite_592txr_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_592txr_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_592txr_const.te_long) < + ws_protocol_acurite_592txr_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_592txr_const.te_short) < + ws_protocol_acurite_592txr_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_592TXRDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_592TXRDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_592txr_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_592TXR* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} + +void* ws_protocol_encoder_acurite_592txr_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolEncoderAcurite_592TXR* instance = malloc(sizeof(WSProtocolEncoderAcurite_592TXR)); + + instance->base.protocol = &ws_protocol_acurite_592txr; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 6; + instance->encoder.size_upload = + ws_protocol_acurite_592txr_const.min_count_bit_for_found * 2 + 8; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void ws_protocol_encoder_acurite_592txr_free(void* context) { + furi_assert(context); + WSProtocolEncoderAcurite_592TXR* instance = context; + free(instance->encoder.upload); + free(instance); +} + +bool ws_protocol_encoder_acurite_592txr_get_upload(WSProtocolEncoderAcurite_592TXR* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 8; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_short * 3); + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_long); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_acurite_592txr_const.te_long); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_acurite_592txr_const.te_short); + } + } + + return true; +} + +SubGhzProtocolStatus + ws_protocol_encoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolEncoderAcurite_592TXR* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if((instance->generic.data_count_bit > + ws_protocol_acurite_592txr_const.min_count_bit_for_found)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 6); + + if(!ws_protocol_encoder_acurite_592txr_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +LevelDuration ws_protocol_encoder_acurite_592txr_yield(void* context) { + WSProtocolEncoderAcurite_592TXR* 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; +} + +void ws_protocol_encoder_acurite_592txr_stop(void* context) { + WSProtocolEncoderAcurite_592TXR* instance = context; + instance->encoder.is_running = false; +} diff --git a/lib/subghz/protocols/acurite_592txr.h b/lib/subghz/protocols/acurite_592txr.h new file mode 100644 index 00000000000..08dcbc6177c --- /dev/null +++ b/lib/subghz/protocols/acurite_592txr.h @@ -0,0 +1,88 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_592TXR_NAME "Acurite 592TXR" + +typedef struct WSProtocolDecoderAcurite_592TXR WSProtocolDecoderAcurite_592TXR; +typedef struct WSProtocolEncoderAcurite_592TXR WSProtocolEncoderAcurite_592TXR; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_592txr_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_592txr_encoder; +extern const SubGhzProtocol ws_protocol_acurite_592txr; + +/** + * Allocate WSProtocolDecoderAcurite_592TXR. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_592TXR* pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void* ws_protocol_decoder_acurite_592txr_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + */ +void ws_protocol_decoder_acurite_592txr_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_592txr_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_acurite_592txr_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_acurite_592txr_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_592TXR. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_592TXR instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* output); + +void* ws_protocol_encoder_acurite_592txr_alloc(SubGhzEnvironment* environment); +void ws_protocol_encoder_acurite_592txr_free(void* context); +bool ws_protocol_encoder_acurite_592txr_get_upload(WSProtocolEncoderAcurite_592TXR* instance); +SubGhzProtocolStatus + ws_protocol_encoder_acurite_592txr_deserialize(void* context, FlipperFormat* flipper_format); +LevelDuration ws_protocol_encoder_acurite_592txr_yield(void* context); +void ws_protocol_encoder_acurite_592txr_stop(void* context); \ No newline at end of file diff --git a/lib/subghz/protocols/acurite_606tx.c b/lib/subghz/protocols/acurite_606tx.c new file mode 100644 index 00000000000..93eacca332f --- /dev/null +++ b/lib/subghz/protocols/acurite_606tx.c @@ -0,0 +1,242 @@ +#include "acurite_606tx.h" + +#define TAG "WSProtocolAcurite_606TX" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | cccc cccc + * - i: identification; changes on battery switch + * - c: lfsr_digest8; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - t: Temperature; in °C + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_606tx_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderAcurite_606TX { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_606TX { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_606TXDecoderStepReset = 0, + Acurite_606TXDecoderStepSaveDuration, + Acurite_606TXDecoderStepCheckDuration, +} Acurite_606TXDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder = { + .alloc = ws_protocol_decoder_acurite_606tx_alloc, + .free = ws_protocol_decoder_acurite_606tx_free, + + .feed = ws_protocol_decoder_acurite_606tx_feed, + .reset = ws_protocol_decoder_acurite_606tx_reset, + + .get_hash_data = ws_protocol_decoder_acurite_606tx_get_hash_data, + .serialize = ws_protocol_decoder_acurite_606tx_serialize, + .deserialize = ws_protocol_decoder_acurite_606tx_deserialize, + .get_string = ws_protocol_decoder_acurite_606tx_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_606tx = { + .name = WS_PROTOCOL_ACURITE_606TX_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_acurite_606tx_decoder, + .encoder = &ws_protocol_acurite_606tx_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_606TX* instance = malloc(sizeof(WSProtocolDecoderAcurite_606TX)); + instance->base.protocol = &ws_protocol_acurite_606tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_606tx_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_606tx_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; +} + +static bool ws_protocol_acurite_606tx_check(WSProtocolDecoderAcurite_606TX* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 3, 0x98, 0xF1); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_606tx_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 24) & 0xFF; + instance->battery_low = (instance->data >> 23) & 1; + + instance->channel = WS_NO_CHANNEL; + + if(!((instance->data >> 19) & 1)) { + instance->temp = (float)((instance->data >> 8) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 8) & 0x07FF) + 1) / -10.0f; + } + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_606TXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short * 17) < + ws_protocol_acurite_606tx_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_606TXDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_606TXDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + + case Acurite_606TXDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) < + ws_protocol_acurite_606tx_const.te_delta) || + (duration > ws_protocol_acurite_606tx_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_606tx_const.min_count_bit_for_found) && + ws_protocol_acurite_606tx_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_606tx_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long) < + ws_protocol_acurite_606tx_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_long * 2) < + ws_protocol_acurite_606tx_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_606TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_606TXDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_606tx_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_606TX* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/acurite_606tx.h b/lib/subghz/protocols/acurite_606tx.h new file mode 100644 index 00000000000..f41e9b7e24a --- /dev/null +++ b/lib/subghz/protocols/acurite_606tx.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_606TX_NAME "Acurite-606TX" + +typedef struct WSProtocolDecoderAcurite_606TX WSProtocolDecoderAcurite_606TX; +typedef struct WSProtocolEncoderAcurite_606TX WSProtocolEncoderAcurite_606TX; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_606tx_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_606tx_encoder; +extern const SubGhzProtocol ws_protocol_acurite_606tx; + +/** + * Allocate WSProtocolDecoderAcurite_606TX. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_606TX* pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void* ws_protocol_decoder_acurite_606tx_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + */ +void ws_protocol_decoder_acurite_606tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_606tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_acurite_606tx_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_acurite_606tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_606TX. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_606TX instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/acurite_609txc.c b/lib/subghz/protocols/acurite_609txc.c new file mode 100644 index 00000000000..126f8e20131 --- /dev/null +++ b/lib/subghz/protocols/acurite_609txc.c @@ -0,0 +1,242 @@ +#include "acurite_609txc.h" + +#define TAG "WSProtocolAcurite_609TXC" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216 + * + * 0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111 + * iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc + * - i: identification; changes on battery switch + * - c: checksum (sum of previous by bytes) + * - u: unknown + * - b: battery low; flag to indicate low battery voltage + * - t: temperature; in °C * 10, 12 bit with complement + * - h: humidity + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_609txc_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderAcurite_609TXC { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_609TXC { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_609TXCDecoderStepReset = 0, + Acurite_609TXCDecoderStepSaveDuration, + Acurite_609TXCDecoderStepCheckDuration, +} Acurite_609TXCDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = { + .alloc = ws_protocol_decoder_acurite_609txc_alloc, + .free = ws_protocol_decoder_acurite_609txc_free, + + .feed = ws_protocol_decoder_acurite_609txc_feed, + .reset = ws_protocol_decoder_acurite_609txc_reset, + + .get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data, + .serialize = ws_protocol_decoder_acurite_609txc_serialize, + .deserialize = ws_protocol_decoder_acurite_609txc_deserialize, + .get_string = ws_protocol_decoder_acurite_609txc_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_609txc = { + .name = WS_PROTOCOL_ACURITE_609TXC_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_acurite_609txc_decoder, + .encoder = &ws_protocol_acurite_609txc_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC)); + instance->base.protocol = &ws_protocol_acurite_609txc; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_609txc_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_609txc_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; +} + +static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) + + (uint8_t)(instance->decoder.decode_data >> 24) + + (uint8_t)(instance->decoder.decode_data >> 16) + + (uint8_t)(instance->decoder.decode_data >> 8); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + + instance->channel = WS_NO_CHANNEL; + + // Temperature in Celsius is encoded as a 12 bit integer value + // multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2) + // negative values are recovered by sign extend from int16_t. + int16_t temp_raw = + (int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4)); + instance->temp = (temp_raw >> 4) * 0.1f; + instance->humidity = (instance->data >> 8) & 0xff; + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_609TXCDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) < + ws_protocol_acurite_609txc_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_609TXCDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + + case Acurite_609TXCDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) { + if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) < + ws_protocol_acurite_609txc_const.te_delta) || + (duration > ws_protocol_acurite_609txc_const.te_long * 3)) { + //Found syncPostfix + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_609txc_const.min_count_bit_for_found) && + ws_protocol_acurite_609txc_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_609txc_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) < + ws_protocol_acurite_609txc_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) < + ws_protocol_acurite_609txc_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_609TXCDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_acurite_609txc_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_609TXC* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 40), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/acurite_609txc.h b/lib/subghz/protocols/acurite_609txc.h new file mode 100644 index 00000000000..7928b5f7cb4 --- /dev/null +++ b/lib/subghz/protocols/acurite_609txc.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC" + +typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC; +typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder; +extern const SubGhzProtocol ws_protocol_acurite_609txc; + +/** + * Allocate WSProtocolDecoderAcurite_609TXC. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + */ +void ws_protocol_decoder_acurite_609txc_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_acurite_609txc_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_609TXC. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/acurite_986.c b/lib/subghz/protocols/acurite_986.c new file mode 100644 index 00000000000..7c8edd211b9 --- /dev/null +++ b/lib/subghz/protocols/acurite_986.c @@ -0,0 +1,274 @@ +#include "acurite_986.h" + +#define TAG "WSProtocolAcurite_986" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L1644 + * + * 0110 0100 | 1010 1011 | 0110 0010 | 0000 0000 | 0111 0110 + * tttt tttt | IIII IIII | iiii iiii | nbuu uuuu | cccc cccc + * - t: temperature in °F + * - I: identification (high byte) + * - i: identification (low byte) + * - n: sensor number + * - b: battery low flag to indicate low battery voltage + * - u: unknown + * - c: CRC (CRC-8 poly 0x07, little-endian) + * + * bits are sent and shown above LSB first + * identification changes on battery switch + */ + +static const SubGhzBlockConst ws_protocol_acurite_986_const = { + .te_short = 800, + .te_long = 1750, + .te_delta = 50, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderAcurite_986 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAcurite_986 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_986DecoderStepReset = 0, + Acurite_986DecoderStepSync1, + Acurite_986DecoderStepSync2, + Acurite_986DecoderStepSync3, + Acurite_986DecoderStepSaveDuration, + Acurite_986DecoderStepCheckDuration, +} Acurite_986DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_986_decoder = { + .alloc = ws_protocol_decoder_acurite_986_alloc, + .free = ws_protocol_decoder_acurite_986_free, + + .feed = ws_protocol_decoder_acurite_986_feed, + .reset = ws_protocol_decoder_acurite_986_reset, + + .get_hash_data = ws_protocol_decoder_acurite_986_get_hash_data, + .serialize = ws_protocol_decoder_acurite_986_serialize, + .deserialize = ws_protocol_decoder_acurite_986_deserialize, + .get_string = ws_protocol_decoder_acurite_986_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_986_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_986 = { + .name = WS_PROTOCOL_ACURITE_986_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_acurite_986_decoder, + .encoder = &ws_protocol_acurite_986_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_acurite_986_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_986* instance = malloc(sizeof(WSProtocolDecoderAcurite_986)); + instance->base.protocol = &ws_protocol_acurite_986; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_986_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_986_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + instance->decoder.parser_step = Acurite_986DecoderStepReset; +} + +static bool ws_protocol_acurite_986_check(WSProtocolDecoderAcurite_986* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + 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, 4, 0x07, 0x00); + return (crc == (instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_986_remote_controller(WSBlockGeneric* instance) { + int temp; + + instance->id = subghz_protocol_blocks_reverse_key(instance->data >> 24, 8); + instance->id = (instance->id << 8) | + subghz_protocol_blocks_reverse_key(instance->data >> 16, 8); + instance->battery_low = (instance->data >> 14) & 1; + instance->channel = ((instance->data >> 15) & 1) + 1; + + temp = subghz_protocol_blocks_reverse_key(instance->data >> 32, 8); + if(temp & 0x80) { + temp = -(temp & 0x7F); + } + instance->temp = locale_fahrenheit_to_celsius((float)temp); + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_acurite_986_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_986DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_986_const.te_long) < + ws_protocol_acurite_986_const.te_delta * 15)) { + //Found 1st sync bit + instance->decoder.parser_step = Acurite_986DecoderStepSync1; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Acurite_986DecoderStepSync1: + if(DURATION_DIFF(duration, ws_protocol_acurite_986_const.te_long) < + ws_protocol_acurite_986_const.te_delta * 15) { + if(!level) { + instance->decoder.parser_step = Acurite_986DecoderStepSync2; + } + } else { + instance->decoder.parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSync2: + if(DURATION_DIFF(duration, ws_protocol_acurite_986_const.te_long) < + ws_protocol_acurite_986_const.te_delta * 15) { + if(!level) { + instance->decoder.parser_step = Acurite_986DecoderStepSync3; + } + } else { + instance->decoder.parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSync3: + if(DURATION_DIFF(duration, ws_protocol_acurite_986_const.te_long) < + ws_protocol_acurite_986_const.te_delta * 15) { + if(!level) { + instance->decoder.parser_step = Acurite_986DecoderStepSaveDuration; + } + } else { + instance->decoder.parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_986DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_986DecoderStepReset; + } + break; + + case Acurite_986DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_acurite_986_const.te_short) < + ws_protocol_acurite_986_const.te_delta * 10) { + if(duration < ws_protocol_acurite_986_const.te_short) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_986DecoderStepSaveDuration; + } else { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_986DecoderStepSaveDuration; + } + } else { + //Found syncPostfix + instance->decoder.parser_step = Acurite_986DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_986_const.min_count_bit_for_found) && + ws_protocol_acurite_986_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_986_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } else { + instance->decoder.parser_step = Acurite_986DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_acurite_986_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_acurite_986_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_acurite_986_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_acurite_986_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_acurite_986_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_986* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/acurite_986.h b/lib/subghz/protocols/acurite_986.h new file mode 100644 index 00000000000..7f37235f498 --- /dev/null +++ b/lib/subghz/protocols/acurite_986.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_986_NAME "Acurite-986" + +typedef struct WSProtocolDecoderAcurite_986 WSProtocolDecoderAcurite_986; +typedef struct WSProtocolEncoderAcurite_986 WSProtocolEncoderAcurite_986; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_986_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_986_encoder; +extern const SubGhzProtocol ws_protocol_acurite_986; + +/** + * Allocate WSProtocolDecoderAcurite_986. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_986* pointer to a WSProtocolDecoderAcurite_986 instance + */ +void* ws_protocol_decoder_acurite_986_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_986. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + */ +void ws_protocol_decoder_acurite_986_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_986. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + */ +void ws_protocol_decoder_acurite_986_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_986_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_acurite_986_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_986. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_acurite_986_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_986. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_acurite_986_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_986 instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_986_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/alutech_at_4n.c b/lib/subghz/protocols/alutech_at_4n.c index 2a16cae8505..0f29293f96d 100644 --- a/lib/subghz/protocols/alutech_at_4n.c +++ b/lib/subghz/protocols/alutech_at_4n.c @@ -651,10 +651,10 @@ static void subghz_protocol_alutech_at_4n_remote_controller( subghz_custom_btn_set_max(4); } -uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderAlutech_at_4n* instance = context; - return (uint8_t)instance->crc; + return instance->crc; } SubGhzProtocolStatus subghz_protocol_decoder_alutech_at_4n_serialize( diff --git a/lib/subghz/protocols/alutech_at_4n.h b/lib/subghz/protocols/alutech_at_4n.h index a57152556ad..63abc52391e 100644 --- a/lib/subghz/protocols/alutech_at_4n.h +++ b/lib/subghz/protocols/alutech_at_4n.h @@ -77,7 +77,7 @@ void subghz_protocol_decoder_alutech_at_4n_feed(void* context, bool level, uint3 * @param context Pointer to a SubGhzProtocolDecoderAlutech_at_4n instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_alutech_at_4n_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderAlutech_at_4n. diff --git a/lib/subghz/protocols/ambient_weather.c b/lib/subghz/protocols/ambient_weather.c new file mode 100644 index 00000000000..59a359d3ba0 --- /dev/null +++ b/lib/subghz/protocols/ambient_weather.c @@ -0,0 +1,271 @@ +#include "ambient_weather.h" +#include + +#define TAG "WSProtocolAmbient_Weather" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c + * + * Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH. + * Devices supported: + * - Ambient Weather F007TH Thermo-Hygrometer. + * - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer. + * - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054, + * - SwitchDoc Labs F016TH. + * This decoder handles the 433mhz/868mhz thermo-hygrometers. + * The 915mhz (WH*) family of devices use different modulation/encoding. + * Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 + * xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM + * - x: Unknown 0x04 on F007TH/F012TH + * - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH + * - I: ID byte (8 bits), volatie, changes at power up, + * - B: Battery Low + * - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting + * - T: Temperature 12 bits - Fahrenheit * 10 + 400 + * - H: Humidity (8 bits) + * - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64 + * + * three repeats without gap + * full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146) + * and on decoding also 0xffd45 + */ + +#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46 +#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146 +#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000 + +static const SubGhzBlockConst ws_protocol_ambient_weather_const = { + .te_short = 500, + .te_long = 1000, + .te_delta = 120, + .min_count_bit_for_found = 48, +}; + +struct WSProtocolDecoderAmbient_Weather { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_saved_state; + uint16_t header_count; +}; + +struct WSProtocolEncoderAmbient_Weather { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = { + .alloc = ws_protocol_decoder_ambient_weather_alloc, + .free = ws_protocol_decoder_ambient_weather_free, + + .feed = ws_protocol_decoder_ambient_weather_feed, + .reset = ws_protocol_decoder_ambient_weather_reset, + + .get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data, + .serialize = ws_protocol_decoder_ambient_weather_serialize, + .deserialize = ws_protocol_decoder_ambient_weather_deserialize, + .get_string = ws_protocol_decoder_ambient_weather_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_ambient_weather = { + .name = WS_PROTOCOL_AMBIENT_WEATHER_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_ambient_weather_decoder, + .encoder = &ws_protocol_ambient_weather_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather)); + instance->base.protocol = &ws_protocol_ambient_weather; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_ambient_weather_free(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + free(instance); +} + +void ws_protocol_decoder_ambient_weather_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); +} + +static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) { + uint8_t msg[] = { + 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_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64; + return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 32) & 0xFF; + instance->battery_low = (instance->data >> 31) & 1; + instance->channel = ((instance->data >> 28) & 0x07) + 1; + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f); + instance->humidity = (instance->data >> 8) & 0xFF; + instance->btn = WS_NO_BTN; + + // ToDo maybe it won't be needed + /* + Sanity checks to reduce false positives and other bad data + Packets with Bad data often pass the MIC check. + - humidity > 100 (such as 255) and + - temperatures > 140 F (such as 369.5 F and 348.8 F + Specs in the F007TH and F012TH manuals state the range is: + - Temperature: -40 to 140 F + - Humidity: 10 to 99% + @todo - sanity check b[0] "model number" + - 0x45 - F007TH and F012TH + - 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5) + - ? - TFA 30.3208.02 + if (instance->humidity < 0 || instance->humidity > 100) { + ERROR; + } + + if (instance->temp < -40.0 || instance->temp > 140.0) { + ERROR; + } + */ +} + +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + + ManchesterEvent event = ManchesterEventReset; + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongLow; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) < + ws_protocol_ambient_weather_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) < + ws_protocol_ambient_weather_const.te_delta * 2) { + event = ManchesterEventLongHigh; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_saved_state, event, &instance->manchester_saved_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + } + + if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_1) || + ((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) == + AMBIENT_WEATHER_PACKET_HEADER_2)) { + if(ws_protocol_ambient_weather_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = + ws_protocol_ambient_weather_const.min_count_bit_for_found; + ws_protocol_ambient_weather_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } + } else { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_saved_state, + ManchesterEventReset, + &instance->manchester_saved_state, + NULL); + } +} + +uint32_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_ambient_weather_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAmbient_Weather* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/ambient_weather.h b/lib/subghz/protocols/ambient_weather.h new file mode 100644 index 00000000000..c9b7436f5f5 --- /dev/null +++ b/lib/subghz/protocols/ambient_weather.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AMBIENT_WEATHER_NAME "Ambient_Weather" + +typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather; +typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather; + +extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder; +extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder; +extern const SubGhzProtocol ws_protocol_ambient_weather; + +/** + * Allocate WSProtocolDecoderAmbient_Weather. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + */ +void ws_protocol_decoder_ambient_weather_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_ambient_weather_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAmbient_Weather. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAmbient_Weather instance + * @param output Resulting text + */ +void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/ansonic.c b/lib/subghz/protocols/ansonic.c index 9a122629be7..571b0ad99f8 100644 --- a/lib/subghz/protocols/ansonic.c +++ b/lib/subghz/protocols/ansonic.c @@ -295,10 +295,10 @@ static void subghz_protocol_ansonic_check_remote_controller(SubGhzBlockGeneric* instance->btn = ((instance->data >> 1) & 0x3); } -uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_ansonic_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderAnsonic* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/ansonic.h b/lib/subghz/protocols/ansonic.h index 9558531877d..6e9d9b990d0 100644 --- a/lib/subghz/protocols/ansonic.h +++ b/lib/subghz/protocols/ansonic.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_ansonic_feed(void* context, bool level, uint32_t du * @param context Pointer to a SubGhzProtocolDecoderAnsonic instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_ansonic_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_ansonic_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderAnsonic. diff --git a/lib/subghz/protocols/auriol_ahfl.c b/lib/subghz/protocols/auriol_ahfl.c new file mode 100644 index 00000000000..b318c3d34f6 --- /dev/null +++ b/lib/subghz/protocols/auriol_ahfl.c @@ -0,0 +1,257 @@ +#include "auriol_ahfl.h" + +#define TAG "WSProtocolAuriol_AHFL" + +/* + * +Auriol AHFL 433B2 IPX4 sensor. + +Data layout: + IIIIIIII-B-X-CC-TTTTTTTTTTTT-HHHHHHH0-FFFF-SSSSSS +Exmpl.: 10111100-1-0-00-000011101000-01101100-0100-001011 + +- I: id, 8 bit +- B: where B is the battery status: 1=OK, 0=LOW, 1 bit +- X: tx-button, 1 bit (might not work) +- C: CC is the channel: 00=CH1, 01=CH2, 11=CH3, 2bit +- T: temperature, 12 bit: 2's complement, scaled by 10 +- H: humidity, 7 bits data, 1 bit 0 +- F: always 0x4 (0100) +- S: nibble sum, 6 bits + + * The sensor sends 42 bits 5 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * + */ + +#define AURIOL_AHFL_CONST_DATA 0b0100 + +static const SubGhzBlockConst ws_protocol_auriol_ahfl_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 42, +}; + +struct WSProtocolDecoderAuriol_AHFL { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAuriol_AHFL { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + auriol_AHFLDecoderStepReset = 0, + auriol_AHFLDecoderStepSaveDuration, + auriol_AHFLDecoderStepCheckDuration, +} auriol_AHFLDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_auriol_ahfl_decoder = { + .alloc = ws_protocol_decoder_auriol_ahfl_alloc, + .free = ws_protocol_decoder_auriol_ahfl_free, + + .feed = ws_protocol_decoder_auriol_ahfl_feed, + .reset = ws_protocol_decoder_auriol_ahfl_reset, + + .get_hash_data = ws_protocol_decoder_auriol_ahfl_get_hash_data, + .serialize = ws_protocol_decoder_auriol_ahfl_serialize, + .deserialize = ws_protocol_decoder_auriol_ahfl_deserialize, + .get_string = ws_protocol_decoder_auriol_ahfl_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_auriol_ahfl_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_auriol_ahfl = { + .name = WS_PROTOCOL_AURIOL_AHFL_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_auriol_ahfl_decoder, + .encoder = &ws_protocol_auriol_ahfl_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_auriol_ahfl_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAuriol_AHFL* instance = malloc(sizeof(WSProtocolDecoderAuriol_AHFL)); + instance->base.protocol = &ws_protocol_auriol_ahfl; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_auriol_ahfl_free(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + free(instance); +} + +void ws_protocol_decoder_auriol_ahfl_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; +} + +static bool ws_protocol_auriol_ahfl_check(WSProtocolDecoderAuriol_AHFL* instance) { + uint8_t type = (instance->decoder.decode_data >> 6) & 0x0F; + + if(type != AURIOL_AHFL_CONST_DATA) { + // Fail const data check + return false; + } + + uint64_t payload = instance->decoder.decode_data >> 6; + // Checksum is the last 6 bits of data + uint8_t checksum_received = instance->decoder.decode_data & 0x3F; + uint8_t checksum_calculated = 0; + for(uint8_t i = 0; i < 9; i++) { + checksum_calculated += (payload >> (i * 4)) & 0xF; + } + return checksum_received == checksum_calculated; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_auriol_ahfl_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 34; + instance->battery_low = (instance->data >> 33) & 1; + instance->btn = (instance->data >> 32) & 1; + instance->channel = ((instance->data >> 30) & 0x3) + 1; + if(!((instance->data >> 29) & 1)) { + instance->temp = (float)((instance->data >> 18) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 18) & 0x07FF) + 1) / -10.0f; + } + instance->humidity = (instance->data >> 11) & 0x7F; +} + +void ws_protocol_decoder_auriol_ahfl_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + + switch(instance->decoder.parser_step) { + case auriol_AHFLDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_auriol_ahfl_const.te_short * 18) < + ws_protocol_auriol_ahfl_const.te_delta)) { + //Found syncPrefix + instance->decoder.parser_step = auriol_AHFLDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + case auriol_AHFLDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = auriol_AHFLDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; + } + break; + case auriol_AHFLDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_ahfl_const.te_short) < + ws_protocol_auriol_ahfl_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_auriol_ahfl_const.te_short * 18) < + ws_protocol_auriol_ahfl_const.te_delta * 8) { + //Found syncPostfix + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_auriol_ahfl_const.min_count_bit_for_found) && + ws_protocol_auriol_ahfl_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_auriol_ahfl_remote_controller(&instance->generic); + if(instance->base.callback) { + instance->base.callback(&instance->base, instance->base.context); + } + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = auriol_AHFLDecoderStepSaveDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_auriol_ahfl_const.te_long) < + ws_protocol_auriol_ahfl_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = auriol_AHFLDecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_auriol_ahfl_const.te_long * 2) < + ws_protocol_auriol_ahfl_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = auriol_AHFLDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; + } + } else { + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; + } + } else { + instance->decoder.parser_step = auriol_AHFLDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_auriol_ahfl_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_auriol_ahfl_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_auriol_ahfl_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_auriol_ahfl_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_auriol_ahfl_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAuriol_AHFL* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/auriol_ahfl.h b/lib/subghz/protocols/auriol_ahfl.h new file mode 100644 index 00000000000..e1b6e2b4720 --- /dev/null +++ b/lib/subghz/protocols/auriol_ahfl.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AURIOL_AHFL_NAME "Auriol AHFL" //Auriol AHFL 433B2 IPX4 + +typedef struct WSProtocolDecoderAuriol_AHFL WSProtocolDecoderAuriol_AHFL; +typedef struct WSProtocolEncoderAuriol_AHFL WSProtocolEncoderAuriol_AHFL; + +extern const SubGhzProtocolDecoder ws_protocol_auriol_ahfl_decoder; +extern const SubGhzProtocolEncoder ws_protocol_auriol_ahfl_encoder; +extern const SubGhzProtocol ws_protocol_auriol_ahfl; + +/** + * Allocate WSProtocolDecoderAuriol_AHFL. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAuriol_AHFL* pointer to a WSProtocolDecoderAuriol_AHFL instance + */ +void* ws_protocol_decoder_auriol_ahfl_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAuriol_AHFL. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + */ +void ws_protocol_decoder_auriol_ahfl_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAuriol_AHFL. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + */ +void ws_protocol_decoder_auriol_ahfl_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_auriol_ahfl_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_auriol_ahfl_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAuriol_AHFL. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_auriol_ahfl_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAuriol_AHFL. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_auriol_ahfl_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAuriol_AHFL instance + * @param output Resulting text + */ +void ws_protocol_decoder_auriol_ahfl_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/auriol_hg0601a.c b/lib/subghz/protocols/auriol_hg0601a.c new file mode 100644 index 00000000000..5d3a485592a --- /dev/null +++ b/lib/subghz/protocols/auriol_hg0601a.c @@ -0,0 +1,251 @@ +#include "auriol_hg0601a.h" + +#define TAG "WSProtocolAuriol_TH" + +/* + * +Auriol HG06061A-DCF-TX sensor. + +Data layout: + DDDDDDDD-B0-NN-TT-TTTTTTTTTT-CCCC-HHHHHHHH +Exmpl.: 11110100-10-01-00-0001001100-1111-01011101 + +- D: id, 8 bit +- B: where B is the battery status: 1=OK, 0=LOW, 1 bit +- 0: just zero :) +- N: NN is the channel: 00=CH1, 01=CH2, 11=CH3, 2bit +- T: temperature, 12 bit: 2's complement, scaled by 10 +- C: 4 bit: seems to be 0xf constantly, a separator between temp and humidity +- H: humidity sensor, humidity is 8 bits + + * The sensor sends 37 bits 10 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * + */ + +#define AURIOL_TH_CONST_DATA 0b1110 + +static const SubGhzBlockConst ws_protocol_auriol_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderAuriol_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderAuriol_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + auriol_THDecoderStepReset = 0, + auriol_THDecoderStepSaveDuration, + auriol_THDecoderStepCheckDuration, +} auriol_THDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder = { + .alloc = ws_protocol_decoder_auriol_th_alloc, + .free = ws_protocol_decoder_auriol_th_free, + + .feed = ws_protocol_decoder_auriol_th_feed, + .reset = ws_protocol_decoder_auriol_th_reset, + + .get_hash_data = ws_protocol_decoder_auriol_th_get_hash_data, + .serialize = ws_protocol_decoder_auriol_th_serialize, + .deserialize = ws_protocol_decoder_auriol_th_deserialize, + .get_string = ws_protocol_decoder_auriol_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_auriol_th = { + .name = WS_PROTOCOL_AURIOL_TH_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_auriol_th_decoder, + .encoder = &ws_protocol_auriol_th_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAuriol_TH* instance = malloc(sizeof(WSProtocolDecoderAuriol_TH)); + instance->base.protocol = &ws_protocol_auriol_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_auriol_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_auriol_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + instance->decoder.parser_step = auriol_THDecoderStepReset; +} + +static bool ws_protocol_auriol_th_check(WSProtocolDecoderAuriol_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == AURIOL_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_auriol_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 31) & 0xFF; + instance->battery_low = ((instance->data >> 30) & 1); + instance->channel = ((instance->data >> 25) & 0x03) + 1; + instance->btn = WS_NO_BTN; + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0x7F; +} + +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + + switch(instance->decoder.parser_step) { + case auriol_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta)) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case auriol_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + + case auriol_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 8) < + ws_protocol_auriol_th_const.te_delta) { + //Found sync + instance->decoder.parser_step = auriol_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_auriol_th_const.min_count_bit_for_found) && + ws_protocol_auriol_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_auriol_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = auriol_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 2) < + ws_protocol_auriol_th_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_auriol_th_const.te_short) < + ws_protocol_auriol_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_auriol_th_const.te_short * 4) < + ws_protocol_auriol_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = auriol_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = auriol_THDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_auriol_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_auriol_th_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAuriol_TH* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/auriol_hg0601a.h b/lib/subghz/protocols/auriol_hg0601a.h new file mode 100644 index 00000000000..2f224416d7f --- /dev/null +++ b/lib/subghz/protocols/auriol_hg0601a.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_AURIOL_TH_NAME "Auriol HG06061" //HG06061A-DCF-TX + +typedef struct WSProtocolDecoderAuriol_TH WSProtocolDecoderAuriol_TH; +typedef struct WSProtocolEncoderAuriol_TH WSProtocolEncoderAuriol_TH; + +extern const SubGhzProtocolDecoder ws_protocol_auriol_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_auriol_th_encoder; +extern const SubGhzProtocol ws_protocol_auriol_th; + +/** + * Allocate WSProtocolDecoderAuriol_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAuriol_TH* pointer to a WSProtocolDecoderAuriol_TH instance + */ +void* ws_protocol_decoder_auriol_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + */ +void ws_protocol_decoder_auriol_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_auriol_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_auriol_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_auriol_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAuriol_TH. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_auriol_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAuriol_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_auriol_th_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/base.c b/lib/subghz/protocols/base.c index 37d1a308f06..716ee9299b0 100644 --- a/lib/subghz/protocols/base.c +++ b/lib/subghz/protocols/base.c @@ -53,6 +53,21 @@ SubGhzProtocolStatus subghz_protocol_decoder_base_deserialize( uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base) { uint8_t hash = 0; + if(decoder_base->protocol && decoder_base->protocol->decoder && + decoder_base->protocol->decoder->get_hash_data) { + uint32_t full = decoder_base->protocol->decoder->get_hash_data(decoder_base); + uint8_t* p = (uint8_t*)&full; + for(size_t i = 0; i < sizeof(full); i++) { + hash ^= p[i]; + } + } + + return hash; +} + +uint32_t subghz_protocol_decoder_base_get_hash_data_long(SubGhzProtocolDecoderBase* decoder_base) { + uint32_t hash = 0; + if(decoder_base->protocol && decoder_base->protocol->decoder && decoder_base->protocol->decoder->get_hash_data) { hash = decoder_base->protocol->decoder->get_hash_data(decoder_base); diff --git a/lib/subghz/protocols/base.h b/lib/subghz/protocols/base.h index 1d819ab5efb..3070f873720 100644 --- a/lib/subghz/protocols/base.h +++ b/lib/subghz/protocols/base.h @@ -73,6 +73,13 @@ SubGhzProtocolStatus subghz_protocol_decoder_base_deserialize( */ uint8_t subghz_protocol_decoder_base_get_hash_data(SubGhzProtocolDecoderBase* decoder_base); +/** + * Getting the long hash sum of the last randomly received parcel. + * @param decoder_base Pointer to a SubGhzProtocolDecoderBase instance + * @return hash Hash sum + */ +uint32_t subghz_protocol_decoder_base_get_hash_data_long(SubGhzProtocolDecoderBase* decoder_base); + // Encoder Base typedef struct SubGhzProtocolEncoderBase SubGhzProtocolEncoderBase; diff --git a/lib/subghz/protocols/bett.c b/lib/subghz/protocols/bett.c index 7fce94448e1..054f41ceb58 100644 --- a/lib/subghz/protocols/bett.c +++ b/lib/subghz/protocols/bett.c @@ -289,10 +289,10 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat } } -uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_bett_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderBETT* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/bett.h b/lib/subghz/protocols/bett.h index 0a11cbe6973..e3ba985fec8 100644 --- a/lib/subghz/protocols/bett.h +++ b/lib/subghz/protocols/bett.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_bett_feed(void* context, bool level, uint32_t durat * @param context Pointer to a SubGhzProtocolDecoderBETT instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_bett_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_bett_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderBETT. diff --git a/lib/subghz/protocols/bin_raw.c b/lib/subghz/protocols/bin_raw.c index 5118467976c..58ec69cb7a9 100644 --- a/lib/subghz/protocols/bin_raw.c +++ b/lib/subghz/protocols/bin_raw.c @@ -960,12 +960,19 @@ void subghz_protocol_decoder_bin_raw_data_input_rssi( } } -uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderBinRAW* instance = context; - return subghz_protocol_blocks_add_bytes( - instance->data + instance->data_markup[0].byte_bias, - subghz_protocol_bin_raw_get_full_byte(instance->data_markup[0].bit_count)); + union { + uint32_t full; + uint8_t split[4]; + } hash = {0}; + uint8_t* p = instance->data + instance->data_markup[0].byte_bias; + size_t len = subghz_protocol_bin_raw_get_full_byte(instance->data_markup[0].bit_count); + for(size_t i = 0; i < len; i++) { + hash.split[i % sizeof(hash)] ^= p[i]; + } + return hash.full; } SubGhzProtocolStatus subghz_protocol_decoder_bin_raw_serialize( diff --git a/lib/subghz/protocols/bin_raw.h b/lib/subghz/protocols/bin_raw.h index 26cc6ec3a20..fac9ebfa4d5 100644 --- a/lib/subghz/protocols/bin_raw.h +++ b/lib/subghz/protocols/bin_raw.h @@ -79,7 +79,7 @@ void subghz_protocol_decoder_bin_raw_feed(void* context, bool level, uint32_t du * @param context Pointer to a SubGhzProtocolDecoderBinRAW instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_bin_raw_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderBinRAW. diff --git a/lib/subghz/protocols/came.c b/lib/subghz/protocols/came.c index 40ae05bade3..74ff42f4dba 100644 --- a/lib/subghz/protocols/came.c +++ b/lib/subghz/protocols/came.c @@ -310,10 +310,10 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat } } -uint8_t subghz_protocol_decoder_came_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_came_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderCame* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/came.h b/lib/subghz/protocols/came.h index fffa017ff59..67b701522e8 100644 --- a/lib/subghz/protocols/came.h +++ b/lib/subghz/protocols/came.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_came_feed(void* context, bool level, uint32_t durat * @param context Pointer to a SubGhzProtocolDecoderCame instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_came_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_came_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderCame. diff --git a/lib/subghz/protocols/came_atomo.c b/lib/subghz/protocols/came_atomo.c index 87927e3628b..4ad5ac060cf 100644 --- a/lib/subghz/protocols/came_atomo.c +++ b/lib/subghz/protocols/came_atomo.c @@ -682,10 +682,10 @@ static uint8_t subghz_protocol_came_atomo_get_btn_code() { return btn; } -uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderCameAtomo* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/came_atomo.h b/lib/subghz/protocols/came_atomo.h index c5e45a68d82..79957eac7bf 100644 --- a/lib/subghz/protocols/came_atomo.h +++ b/lib/subghz/protocols/came_atomo.h @@ -81,7 +81,7 @@ void subghz_protocol_decoder_came_atomo_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderCameAtomo instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_came_atomo_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderCameAtomo. diff --git a/lib/subghz/protocols/came_twee.c b/lib/subghz/protocols/came_twee.c index 1d79d2201e2..113343fecd2 100644 --- a/lib/subghz/protocols/came_twee.c +++ b/lib/subghz/protocols/came_twee.c @@ -409,10 +409,10 @@ void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_came_twee_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderCameTwee* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/came_twee.h b/lib/subghz/protocols/came_twee.h index f26f1e8062d..0da0e62076c 100644 --- a/lib/subghz/protocols/came_twee.h +++ b/lib/subghz/protocols/came_twee.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_came_twee_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderCameTwee instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_came_twee_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderCameTwee. diff --git a/lib/subghz/protocols/chamberlain_code.c b/lib/subghz/protocols/chamberlain_code.c index 0dd0d2b0b79..53ec2008815 100644 --- a/lib/subghz/protocols/chamberlain_code.c +++ b/lib/subghz/protocols/chamberlain_code.c @@ -422,10 +422,10 @@ void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderChamb_Code* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/chamberlain_code.h b/lib/subghz/protocols/chamberlain_code.h index c8ffed5c506..01bfe5c0b30 100644 --- a/lib/subghz/protocols/chamberlain_code.h +++ b/lib/subghz/protocols/chamberlain_code.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_chamb_code_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderChamb_Code instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_chamb_code_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderChamb_Code. diff --git a/lib/subghz/protocols/clemsa.c b/lib/subghz/protocols/clemsa.c index a0547a11372..b558f6a9ebe 100644 --- a/lib/subghz/protocols/clemsa.c +++ b/lib/subghz/protocols/clemsa.c @@ -310,10 +310,10 @@ static void subghz_protocol_clemsa_check_remote_controller(SubGhzBlockGeneric* i instance->btn = (instance->data & 0x03); } -uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_clemsa_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderClemsa* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/clemsa.h b/lib/subghz/protocols/clemsa.h index f14cd3dace0..ae5e5757e56 100644 --- a/lib/subghz/protocols/clemsa.h +++ b/lib/subghz/protocols/clemsa.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_clemsa_feed(void* context, bool level, uint32_t dur * @param context Pointer to a SubGhzProtocolDecoderClemsa instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_clemsa_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderClemsa. diff --git a/lib/subghz/protocols/doitrand.c b/lib/subghz/protocols/doitrand.c index 69b8bba4ae1..d9572c0f17a 100644 --- a/lib/subghz/protocols/doitrand.c +++ b/lib/subghz/protocols/doitrand.c @@ -303,10 +303,10 @@ static void subghz_protocol_doitrand_check_remote_controller(SubGhzBlockGeneric* instance->btn = ((instance->data >> 18) & 0x3); } -uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_doitrand_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderDoitrand* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/doitrand.h b/lib/subghz/protocols/doitrand.h index 5dbc6678fdc..43bfef2e24a 100644 --- a/lib/subghz/protocols/doitrand.h +++ b/lib/subghz/protocols/doitrand.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_doitrand_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderDoitrand instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_doitrand_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_doitrand_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderDoitrand. diff --git a/lib/subghz/protocols/dooya.c b/lib/subghz/protocols/dooya.c index 816847840fc..f847be9b810 100644 --- a/lib/subghz/protocols/dooya.c +++ b/lib/subghz/protocols/dooya.c @@ -347,10 +347,10 @@ static void subghz_protocol_dooya_check_remote_controller(SubGhzBlockGeneric* in instance->btn = instance->data & 0xFF; } -uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_dooya_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderDooya* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/dooya.h b/lib/subghz/protocols/dooya.h index ffe9d41eff7..8ada20fbabb 100644 --- a/lib/subghz/protocols/dooya.h +++ b/lib/subghz/protocols/dooya.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_dooya_feed(void* context, bool level, uint32_t dura * @param context Pointer to a SubGhzProtocolDecoderDooya instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_dooya_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_dooya_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderDooya. diff --git a/lib/subghz/protocols/faac_slh.c b/lib/subghz/protocols/faac_slh.c index 2044d9d207a..d90f304feb3 100644 --- a/lib/subghz/protocols/faac_slh.c +++ b/lib/subghz/protocols/faac_slh.c @@ -570,10 +570,10 @@ static void subghz_protocol_faac_slh_check_remote_controller( } } -uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderFaacSLH* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/faac_slh.h b/lib/subghz/protocols/faac_slh.h index 16b6f031e6e..1f0dadf69e7 100644 --- a/lib/subghz/protocols/faac_slh.h +++ b/lib/subghz/protocols/faac_slh.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_faac_slh_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_faac_slh_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderFaacSLH. diff --git a/lib/subghz/protocols/gate_tx.c b/lib/subghz/protocols/gate_tx.c index 2ebd6bb03b9..bfff90e1150 100644 --- a/lib/subghz/protocols/gate_tx.c +++ b/lib/subghz/protocols/gate_tx.c @@ -283,10 +283,10 @@ static void subghz_protocol_gate_tx_check_remote_controller(SubGhzBlockGeneric* instance->btn = ((code_found_reverse >> 16) & 0x0F); } -uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderGateTx* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/gate_tx.h b/lib/subghz/protocols/gate_tx.h index a6abede0d7f..1d539d15556 100644 --- a/lib/subghz/protocols/gate_tx.h +++ b/lib/subghz/protocols/gate_tx.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_gate_tx_feed(void* context, bool level, uint32_t du * @param context Pointer to a SubGhzProtocolDecoderGateTx instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_gate_tx_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderGateTx. diff --git a/lib/subghz/protocols/genie.c b/lib/subghz/protocols/genie.c index 191170e4bdc..0d6a52b9b11 100644 --- a/lib/subghz/protocols/genie.c +++ b/lib/subghz/protocols/genie.c @@ -634,10 +634,10 @@ void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t dura } } -uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_genie_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderGenie* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/genie.h b/lib/subghz/protocols/genie.h index bcec3da4a98..80386f67200 100644 --- a/lib/subghz/protocols/genie.h +++ b/lib/subghz/protocols/genie.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_genie_feed(void* context, bool level, uint32_t dura * @param context Pointer to a SubGhzProtocolDecoderGenie instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_genie_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_genie_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderGenie. diff --git a/lib/subghz/protocols/gt_wt_02.c b/lib/subghz/protocols/gt_wt_02.c new file mode 100644 index 00000000000..c93c9e7f585 --- /dev/null +++ b/lib/subghz/protocols/gt_wt_02.c @@ -0,0 +1,258 @@ +#include "gt_wt_02.h" + +#define TAG "WSProtocolGT_WT02" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_02.c + * + * GT-WT-02 sensor on 433.92MHz. + * Example and frame description provided by https://github.com/ludwich66 + * [01] {37} 34 00 ed 47 60 : 00110100 00000000 11101101 01000111 01100000 + * code, BatOK,not-man-send, Channel1, +23,7�C, 35% + * [01] {37} 34 8f 87 15 90 : 00110100 10001111 10000111 00010101 10010000 + * code, BatOK,not-man-send, Channel1,-12,1�C, 10% + * Humidity: + * - the working range is 20-90 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * SENSOR: GT-WT-02 (ALDI Globaltronics..) + * TYP IIIIIIII BMCCTTTT TTTTTTTT HHHHHHHX XXXXX + * TYPE Description: + * - I = Random Device Code, changes with battery reset + * - B = Battery 0=OK 1=LOW + * - M = Manual Send Button Pressed 0=not pressed 1=pressed + * - C = Channel 00=CH1, 01=CH2, 10=CH3 + * - T = Temperature, 12 Bit 2's complement, scaled by 10 + * - H = Humidity = 7 Bit bin2dez 00-99, Display LL=10%, Display HH=110% (Range 20-90%) + * - X = Checksum, sum modulo 64 + * A Lidl AURIO (from 12/2018) with PCB marking YJ-T12 V02 has two extra bits in front. + * +*/ + +static const SubGhzBlockConst ws_protocol_gt_wt_02_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderGT_WT02 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderGT_WT02 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT02DecoderStepReset = 0, + GT_WT02DecoderStepSaveDuration, + GT_WT02DecoderStepCheckDuration, +} GT_WT02DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder = { + .alloc = ws_protocol_decoder_gt_wt_02_alloc, + .free = ws_protocol_decoder_gt_wt_02_free, + + .feed = ws_protocol_decoder_gt_wt_02_feed, + .reset = ws_protocol_decoder_gt_wt_02_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_02_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_02_serialize, + .deserialize = ws_protocol_decoder_gt_wt_02_deserialize, + .get_string = ws_protocol_decoder_gt_wt_02_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_02 = { + .name = WS_PROTOCOL_GT_WT_02_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_gt_wt_02_decoder, + .encoder = &ws_protocol_gt_wt_02_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT02* instance = malloc(sizeof(WSProtocolDecoderGT_WT02)); + instance->base.protocol = &ws_protocol_gt_wt_02; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_02_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_02_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + instance->decoder.parser_step = GT_WT02DecoderStepReset; +} + +static bool ws_protocol_gt_wt_02_check(WSProtocolDecoderGT_WT02* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t sum = (instance->decoder.decode_data >> 5) & 0xe; + uint64_t temp_data = instance->decoder.decode_data >> 9; + for(uint8_t i = 0; i < 7; i++) { + sum += (temp_data >> (i * 4)) & 0xF; + } + return ((uint8_t)(instance->decoder.decode_data & 0x3F) == (sum & 0x3F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_02_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 29) & 0xFF; + instance->battery_low = (instance->data >> 28) & 1; + instance->btn = (instance->data >> 27) & 1; + instance->channel = ((instance->data >> 25) & 0x3) + 1; + + if(!((instance->data >> 24) & 1)) { + instance->temp = (float)((instance->data >> 13) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 13) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 6) & 0x7F; + if(instance->humidity <= 10) // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + else if(instance->humidity > 90) // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; +} + +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT02DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8)) { + //Found syncPrefix + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case GT_WT02DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT02DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + + case GT_WT02DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_02_const.te_short) < + ws_protocol_gt_wt_02_const.te_delta) { + if(DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_short * 18) < + ws_protocol_gt_wt_02_const.te_delta * 8) { + //Found syncPostfix + instance->decoder.parser_step = GT_WT02DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_02_const.min_count_bit_for_found) && + ws_protocol_gt_wt_02_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_02_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else if(instance->decoder.decode_count_bit == 1) { + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long) < + ws_protocol_gt_wt_02_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else if( + DURATION_DIFF(duration, ws_protocol_gt_wt_02_const.te_long * 2) < + ws_protocol_gt_wt_02_const.te_delta * 4) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT02DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT02DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_gt_wt_02_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT02* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/gt_wt_02.h b/lib/subghz/protocols/gt_wt_02.h new file mode 100644 index 00000000000..690d2d401ae --- /dev/null +++ b/lib/subghz/protocols/gt_wt_02.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_02_NAME "GT-WT02" + +typedef struct WSProtocolDecoderGT_WT02 WSProtocolDecoderGT_WT02; +typedef struct WSProtocolEncoderGT_WT02 WSProtocolEncoderGT_WT02; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_02_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_02_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_02; + +/** + * Allocate WSProtocolDecoderGT_WT02. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT02* pointer to a WSProtocolDecoderGT_WT02 instance + */ +void* ws_protocol_decoder_gt_wt_02_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + */ +void ws_protocol_decoder_gt_wt_02_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_02_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_gt_wt_02_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_02_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT02. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_02_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT02 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_02_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/gt_wt_03.c b/lib/subghz/protocols/gt_wt_03.c new file mode 100644 index 00000000000..a575735c141 --- /dev/null +++ b/lib/subghz/protocols/gt_wt_03.c @@ -0,0 +1,333 @@ +#include "gt_wt_03.h" + +#define TAG "WSProtocolGT_WT03" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/gt_wt_03.c + * + * + * Globaltronics GT-WT-03 sensor on 433.92MHz. + * The 01-set sensor has 60 ms packet gap with 10 repeats. + * The 02-set sensor has no packet gap with 23 repeats. + * Example: + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes ] + * {41} 17 cf be fa 6a 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-Yes Batt-Changed ] + * {41} 17 cf fe fa ea 80 [ S1 C1 26,1 C 78.9 F 48% Bat-Good Manual-No Batt-Changed ] + * {41} 01 cf 6f 11 b2 80 [ S2 C2 23,8 C 74.8 F 48% Bat-LOW Manual-No ] + * {41} 01 c8 d0 2b 76 80 [ S2 C3 -4,4 C 24.1 F 55% Bat-Good Manual-No Batt-Changed ] + * Format string: + * ID:8h HUM:8d B:b M:b C:2d TEMP:12d CHK:8h 1x + * Data layout: + * TYP IIIIIIII HHHHHHHH BMCCTTTT TTTTTTTT XXXXXXXX + * - I: Random Device Code: changes with battery reset + * - H: Humidity: 8 Bit 00-99, Display LL=10%, Display HH=110% (Range 20-95%) + * - B: Battery: 0=OK 1=LOW + * - M: Manual Send Button Pressed: 0=not pressed, 1=pressed + * - C: Channel: 00=CH1, 01=CH2, 10=CH3 + * - T: Temperature: 12 Bit 2's complement, scaled by 10, range-50.0 C (-50.1 shown as Lo) to +70.0 C (+70.1 C is shown as Hi) + * - X: Checksum, xor shifting key per byte + * Humidity: + * - the working range is 20-95 % + * - if "LL" in display view it sends 10 % + * - if "HH" in display view it sends 110% + * Checksum: + * Per byte xor the key for each 1-bit, shift per bit. Key list per bit, starting at MSB: + * - 0x00 [07] + * - 0x80 [06] + * - 0x40 [05] + * - 0x20 [04] + * - 0x10 [03] + * - 0x88 [02] + * - 0xc4 [01] + * - 0x62 [00] + * Note: this can also be seen as lower byte of a Galois/Fibonacci LFSR-16, gen 0x00, init 0x3100 (or 0x62 if reversed) resetting at every byte. + * Battery voltages: + * - U=<2,65V +- ~5% Battery indicator + * - U=>2.10C +- 5% plausible readings + * - U=2,00V +- ~5% Temperature offset -5°C Humidity offset unknown + * - U=<1,95V +- ~5% does not initialize anymore + * - U=1,90V +- 5% temperature offset -15°C + * - U=1,80V +- 5% Display is showing refresh pattern + * - U=1.75V +- ~5% TX causes cut out + * + */ + +static const SubGhzBlockConst ws_protocol_gt_wt_03_const = { + .te_short = 285, + .te_long = 570, + .te_delta = 120, + .min_count_bit_for_found = 41, +}; + +struct WSProtocolDecoderGT_WT03 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderGT_WT03 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + GT_WT03DecoderStepReset = 0, + GT_WT03DecoderStepCheckPreambule, + GT_WT03DecoderStepSaveDuration, + GT_WT03DecoderStepCheckDuration, +} GT_WT03DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder = { + .alloc = ws_protocol_decoder_gt_wt_03_alloc, + .free = ws_protocol_decoder_gt_wt_03_free, + + .feed = ws_protocol_decoder_gt_wt_03_feed, + .reset = ws_protocol_decoder_gt_wt_03_reset, + + .get_hash_data = ws_protocol_decoder_gt_wt_03_get_hash_data, + .serialize = ws_protocol_decoder_gt_wt_03_serialize, + .deserialize = ws_protocol_decoder_gt_wt_03_deserialize, + .get_string = ws_protocol_decoder_gt_wt_03_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_gt_wt_03 = { + .name = WS_PROTOCOL_GT_WT_03_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_gt_wt_03_decoder, + .encoder = &ws_protocol_gt_wt_03_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderGT_WT03* instance = malloc(sizeof(WSProtocolDecoderGT_WT03)); + instance->base.protocol = &ws_protocol_gt_wt_03; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_gt_wt_03_free(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + free(instance); +} + +void ws_protocol_decoder_gt_wt_03_reset(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + instance->decoder.parser_step = GT_WT03DecoderStepReset; +} + +static bool ws_protocol_gt_wt_03_check_crc(WSProtocolDecoderGT_WT03* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 33, + instance->decoder.decode_data >> 25, + instance->decoder.decode_data >> 17, + instance->decoder.decode_data >> 9}; + + uint8_t sum = 0; + for(unsigned k = 0; k < sizeof(msg); ++k) { + uint8_t data = msg[k]; + uint16_t key = 0x3100; + for(int i = 7; i >= 0; --i) { + // XOR key into sum if data bit is set + if((data >> i) & 1) sum ^= key & 0xff; + // roll the key right + key = (key >> 1); + } + } + return ((sum ^ (uint8_t)((instance->decoder.decode_data >> 1) & 0xFF)) == 0x2D); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_gt_wt_03_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 33; + instance->humidity = (instance->data >> 25) & 0xFF; + + if(instance->humidity <= 10) { // actually the sensors sends 10 below working range of 20% + instance->humidity = 0; + } else if(instance->humidity > 95) { // actually the sensors sends 110 above working range of 90% + instance->humidity = 100; + } + + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } +} + +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + + switch(instance->decoder.parser_step) { + case GT_WT03DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case GT_WT03DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } + break; + + case GT_WT03DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = GT_WT03DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + + case GT_WT03DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short * 3) < + ws_protocol_gt_wt_03_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_gt_wt_03_const.min_count_bit_for_found) && + ws_protocol_gt_wt_03_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_gt_wt_03_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = GT_WT03DecoderStepCheckPreambule; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_gt_wt_03_const.te_long) < + ws_protocol_gt_wt_03_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_gt_wt_03_const.te_short) < + ws_protocol_gt_wt_03_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = GT_WT03DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + } else { + instance->decoder.parser_step = GT_WT03DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_gt_wt_03_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderGT_WT03* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/gt_wt_03.h b/lib/subghz/protocols/gt_wt_03.h new file mode 100644 index 00000000000..22015291a90 --- /dev/null +++ b/lib/subghz/protocols/gt_wt_03.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_GT_WT_03_NAME "GT-WT03" + +typedef struct WSProtocolDecoderGT_WT03 WSProtocolDecoderGT_WT03; +typedef struct WSProtocolEncoderGT_WT03 WSProtocolEncoderGT_WT03; + +extern const SubGhzProtocolDecoder ws_protocol_gt_wt_03_decoder; +extern const SubGhzProtocolEncoder ws_protocol_gt_wt_03_encoder; +extern const SubGhzProtocol ws_protocol_gt_wt_03; + +/** + * Allocate WSProtocolDecoderGT_WT03. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderGT_WT03* pointer to a WSProtocolDecoderGT_WT03 instance + */ +void* ws_protocol_decoder_gt_wt_03_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_free(void* context); + +/** + * Reset decoder WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + */ +void ws_protocol_decoder_gt_wt_03_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_gt_wt_03_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_gt_wt_03_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_gt_wt_03_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderGT_WT03. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_gt_wt_03_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderGT_WT03 instance + * @param output Resulting text + */ +void ws_protocol_decoder_gt_wt_03_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/holtek.c b/lib/subghz/protocols/holtek.c index 294bd124d3c..56ee1555974 100644 --- a/lib/subghz/protocols/holtek.c +++ b/lib/subghz/protocols/holtek.c @@ -315,10 +315,10 @@ static void subghz_protocol_holtek_check_remote_controller(SubGhzBlockGeneric* i } } -uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_holtek_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderHoltek* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/holtek.h b/lib/subghz/protocols/holtek.h index 19081308d3d..3542ba3c7ed 100644 --- a/lib/subghz/protocols/holtek.h +++ b/lib/subghz/protocols/holtek.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_holtek_feed(void* context, bool level, uint32_t dur * @param context Pointer to a SubGhzProtocolDecoderHoltek instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_holtek_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_holtek_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderHoltek. diff --git a/lib/subghz/protocols/holtek_ht12x.c b/lib/subghz/protocols/holtek_ht12x.c index 302b78598b9..9d3796fc9ba 100644 --- a/lib/subghz/protocols/holtek_ht12x.c +++ b/lib/subghz/protocols/holtek_ht12x.c @@ -322,10 +322,10 @@ static void subghz_protocol_holtek_th12x_check_remote_controller(SubGhzBlockGene instance->cnt = (instance->data >> 4) & 0xFF; } -uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderHoltek_HT12X* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/holtek_ht12x.h b/lib/subghz/protocols/holtek_ht12x.h index 500c061aa3c..f9d19479869 100644 --- a/lib/subghz/protocols/holtek_ht12x.h +++ b/lib/subghz/protocols/holtek_ht12x.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_holtek_th12x_feed(void* context, bool level, uint32 * @param context Pointer to a SubGhzProtocolDecoderHoltek_HT12X instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_holtek_th12x_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderHoltek_HT12X. diff --git a/lib/subghz/protocols/honeywell.c b/lib/subghz/protocols/honeywell.c index e76bb2822be..593a9c64ef5 100644 --- a/lib/subghz/protocols/honeywell.c +++ b/lib/subghz/protocols/honeywell.c @@ -145,10 +145,10 @@ void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_honeywell_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderHoneywell* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/honeywell.h b/lib/subghz/protocols/honeywell.h index 8aa35ce6cc4..af648622bb3 100644 --- a/lib/subghz/protocols/honeywell.h +++ b/lib/subghz/protocols/honeywell.h @@ -28,7 +28,7 @@ void subghz_protocol_decoder_honeywell_reset(void* context); void subghz_protocol_decoder_honeywell_feed(void* context, bool level, uint32_t duration); -uint8_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_honeywell_get_hash_data(void* context); SubGhzProtocolStatus subghz_protocol_decoder_honeywell_serialize( void* context, diff --git a/lib/subghz/protocols/honeywell_wdb.c b/lib/subghz/protocols/honeywell_wdb.c index fcf2822011c..f22eec53d94 100644 --- a/lib/subghz/protocols/honeywell_wdb.c +++ b/lib/subghz/protocols/honeywell_wdb.c @@ -337,10 +337,10 @@ static void subghz_protocol_honeywell_wdb_check_remote_controller( instance->lowbat = (uint8_t)((instance->generic.data >> 1) & 0x1); } -uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderHoneywell_WDB* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/honeywell_wdb.h b/lib/subghz/protocols/honeywell_wdb.h index 91728691b05..7361c739e60 100644 --- a/lib/subghz/protocols/honeywell_wdb.h +++ b/lib/subghz/protocols/honeywell_wdb.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_honeywell_wdb_feed(void* context, bool level, uint3 * @param context Pointer to a SubGhzProtocolDecoderHoneywell_WDB instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_honeywell_wdb_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderHoneywell_WDB. diff --git a/lib/subghz/protocols/hormann.c b/lib/subghz/protocols/hormann.c index fc490e9d1c7..280e04d1d87 100644 --- a/lib/subghz/protocols/hormann.c +++ b/lib/subghz/protocols/hormann.c @@ -288,10 +288,10 @@ static void subghz_protocol_hormann_check_remote_controller(SubGhzBlockGeneric* instance->btn = (instance->data >> 4) & 0xF; } -uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_hormann_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderHormann* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/hormann.h b/lib/subghz/protocols/hormann.h index 8cb45aec328..0ad901dec72 100644 --- a/lib/subghz/protocols/hormann.h +++ b/lib/subghz/protocols/hormann.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_hormann_feed(void* context, bool level, uint32_t du * @param context Pointer to a SubGhzProtocolDecoderHormann instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_hormann_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_hormann_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderHormann. diff --git a/lib/subghz/protocols/ido.c b/lib/subghz/protocols/ido.c index 34e5c55a7ea..ff974482512 100644 --- a/lib/subghz/protocols/ido.c +++ b/lib/subghz/protocols/ido.c @@ -173,10 +173,10 @@ static void subghz_protocol_ido_check_remote_controller(SubGhzBlockGeneric* inst instance->btn = (code_fix >> 20) & 0x0F; } -uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_ido_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderIDo* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/ido.h b/lib/subghz/protocols/ido.h index 9493202466b..186b58a92bf 100644 --- a/lib/subghz/protocols/ido.h +++ b/lib/subghz/protocols/ido.h @@ -43,7 +43,7 @@ void subghz_protocol_decoder_ido_feed(void* context, bool level, uint32_t durati * @param context Pointer to a SubGhzProtocolDecoderIDo instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_ido_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_ido_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderIDo. diff --git a/lib/subghz/protocols/infactory.c b/lib/subghz/protocols/infactory.c new file mode 100644 index 00000000000..1b8991afc51 --- /dev/null +++ b/lib/subghz/protocols/infactory.c @@ -0,0 +1,289 @@ +#include "infactory.h" + +#define TAG "WSProtocolInfactory" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/infactory.c + * + * Analysis using Genuino (see http://gitlab.com/hp-uno, e.g. uno_log_433): + * Observed On-Off-Key (OOK) data pattern: + * preamble syncPrefix data...(40 bit) syncPostfix + * HHLL HHLL HHLL HHLL HLLLLLLLLLLLLLLLL (HLLLL HLLLLLLLL HLLLL HLLLLLLLL ....) HLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL + * Breakdown: + * - four preamble pairs '1'/'0' each with a length of ca. 1000us + * - syncPre, syncPost, data0, data1 have a '1' start pulse of ca. 500us + * - syncPre pulse before dataPtr has a '0' pulse length of ca. 8000us + * - data0 (0-bits) have then a '0' pulse length of ca. 2000us + * - data1 (1-bits) have then a '0' pulse length of ca. 4000us + * - syncPost after dataPtr has a '0' pulse length of ca. 16000us + * This analysis is the reason for the new r_device definitions below. + * NB: pulse_slicer_ppm does not use .gap_limit if .tolerance is set. + * + * Outdoor sensor, transmits temperature and humidity data + * - inFactory NC-3982-913/NX-5817-902, Pearl (for FWS-686 station) + * - nor-tec 73383 (weather station + sensor), Schou Company AS, Denmark + * - DAY 73365 (weather station + sensor), Schou Company AS, Denmark + * Known brand names: inFactory, nor-tec, GreenBlue, DAY. Manufacturer in China. + * Transmissions includes an id. Every 60 seconds the sensor transmits 6 packets: + * 0000 1111 | 0011 0000 | 0101 1100 | 1110 0111 | 0110 0001 + * iiii iiii | cccc ub?? | tttt tttt | tttt hhhh | hhhh ??nn + * - i: identification; changes on battery switch + * - c: CRC-4; CCITT checksum, see below for computation specifics + * - u: unknown; (sometimes set at power-on, but not always) + * - b: battery low; flag to indicate low battery voltage + * - h: Humidity; BCD-encoded, each nibble is one digit, 'A0' means 100%rH + * - t: Temperature; in °F as binary number with one decimal place + 90 °F offset + * - n: Channel; Channel number 1 - 3 + * + */ + +static const SubGhzBlockConst ws_protocol_infactory_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderInfactory { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderInfactory { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + InfactoryDecoderStepReset = 0, + InfactoryDecoderStepCheckPreambule, + InfactoryDecoderStepSaveDuration, + InfactoryDecoderStepCheckDuration, +} InfactoryDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_infactory_decoder = { + .alloc = ws_protocol_decoder_infactory_alloc, + .free = ws_protocol_decoder_infactory_free, + + .feed = ws_protocol_decoder_infactory_feed, + .reset = ws_protocol_decoder_infactory_reset, + + .get_hash_data = ws_protocol_decoder_infactory_get_hash_data, + .serialize = ws_protocol_decoder_infactory_serialize, + .deserialize = ws_protocol_decoder_infactory_deserialize, + .get_string = ws_protocol_decoder_infactory_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_infactory_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_infactory = { + .name = WS_PROTOCOL_INFACTORY_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_infactory_decoder, + .encoder = &ws_protocol_infactory_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderInfactory* instance = malloc(sizeof(WSProtocolDecoderInfactory)); + instance->base.protocol = &ws_protocol_infactory; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_infactory_free(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + free(instance); +} + +void ws_protocol_decoder_infactory_reset(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + instance->decoder.parser_step = InfactoryDecoderStepReset; +} + +static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 32, + (((instance->decoder.decode_data >> 24) & 0x0F) | (instance->decoder.decode_data & 0x0F) + << 4), + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8, + instance->decoder.decode_data}; + + uint8_t crc = + subghz_protocol_blocks_crc4(msg, 4, 0x13, 0); // Koopmann 0x9, CCITT-4; FP-4; ITU-T G.704 + crc ^= msg[4] >> 4; // last nibble is only XORed + return (crc == ((instance->decoder.decode_data >> 28) & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 32; + instance->battery_low = (instance->data >> 26) & 1; + instance->btn = WS_NO_BTN; + instance->temp = + locale_fahrenheit_to_celsius(((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f); + instance->humidity = + (((instance->data >> 8) & 0x0F) * 10) + ((instance->data >> 4) & 0x0F); // BCD, 'A0'=100%rH + instance->channel = instance->data & 0x03; +} + +void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + + switch(instance->decoder.parser_step) { + case InfactoryDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + instance->decoder.parser_step = InfactoryDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case InfactoryDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 2) < + ws_protocol_infactory_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_short * 16) < + ws_protocol_infactory_const.te_delta * 8)) { + //Found syncPrefix + if(instance->header_count > 3) { + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } + break; + + case InfactoryDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = InfactoryDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + + case InfactoryDecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_infactory_const.te_short * 30)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_infactory_const.min_count_bit_for_found) && + ws_protocol_infactory_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_infactory_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = InfactoryDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long) < + ws_protocol_infactory_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_infactory_const.te_short) < + ws_protocol_infactory_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_infactory_const.te_long * 2) < + ws_protocol_infactory_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = InfactoryDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + } else { + instance->decoder.parser_step = InfactoryDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_infactory_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_infactory_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderInfactory* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/infactory.h b/lib/subghz/protocols/infactory.h new file mode 100644 index 00000000000..13e1c883fae --- /dev/null +++ b/lib/subghz/protocols/infactory.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_INFACTORY_NAME "inFactory-TH" + +typedef struct WSProtocolDecoderInfactory WSProtocolDecoderInfactory; +typedef struct WSProtocolEncoderInfactory WSProtocolEncoderInfactory; + +extern const SubGhzProtocolDecoder ws_protocol_infactory_decoder; +extern const SubGhzProtocolEncoder ws_protocol_infactory_encoder; +extern const SubGhzProtocol ws_protocol_infactory; + +/** + * Allocate WSProtocolDecoderInfactory. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderInfactory* pointer to a WSProtocolDecoderInfactory instance + */ +void* ws_protocol_decoder_infactory_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_free(void* context); + +/** + * Reset decoder WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + */ +void ws_protocol_decoder_infactory_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_infactory_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_infactory_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_infactory_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderInfactory. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_infactory_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderInfactory instance + * @param output Resulting text + */ +void ws_protocol_decoder_infactory_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/intertechno_v3.c b/lib/subghz/protocols/intertechno_v3.c index 7fe95299555..dae04cd94a4 100644 --- a/lib/subghz/protocols/intertechno_v3.c +++ b/lib/subghz/protocols/intertechno_v3.c @@ -399,10 +399,10 @@ static void subghz_protocol_intertechno_v3_check_remote_controller(SubGhzBlockGe } } -uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderIntertechno_V3* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/intertechno_v3.h b/lib/subghz/protocols/intertechno_v3.h index 4d1c24cb6b2..adc11c181c7 100644 --- a/lib/subghz/protocols/intertechno_v3.h +++ b/lib/subghz/protocols/intertechno_v3.h @@ -79,7 +79,7 @@ void subghz_protocol_decoder_intertechno_v3_feed(void* context, bool level, uint * @param context Pointer to a SubGhzProtocolDecoderIntertechno_V3 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_intertechno_v3_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderIntertechno_V3. diff --git a/lib/subghz/protocols/kedsum_th.c b/lib/subghz/protocols/kedsum_th.c new file mode 100644 index 00000000000..d657e2e2a6f --- /dev/null +++ b/lib/subghz/protocols/kedsum_th.c @@ -0,0 +1,297 @@ +#include "kedsum_th.h" + +#define TAG "WSProtocolKedsumTH" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/kedsum.c + * + * Frame structure: + * + * Byte: 0 1 2 3 4 + * Nibble: 1 2 3 4 5 6 7 8 9 10 + * Type: 00 IIIIIIII BBCC++++ ttttTTTT hhhhHHHH FFFFXXXX + * + * - I: unique id. changes on powercycle + * - B: Battery state 10 = Ok, 01 = weak, 00 = bad + * - C: channel, 00 = ch1, 10=ch3 + * - + low temp nibble + * - t: med temp nibble + * - T: high temp nibble + * - h: humidity low nibble + * - H: humidity high nibble + * - F: flags + * - X: CRC-4 poly 0x3 init 0x0 xor last 4 bits + */ + +static const SubGhzBlockConst ws_protocol_kedsum_th_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 42, +}; + +struct WSProtocolDecoderKedsumTH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderKedsumTH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + KedsumTHDecoderStepReset = 0, + KedsumTHDecoderStepCheckPreambule, + KedsumTHDecoderStepSaveDuration, + KedsumTHDecoderStepCheckDuration, +} KedsumTHDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_kedsum_th_decoder = { + .alloc = ws_protocol_decoder_kedsum_th_alloc, + .free = ws_protocol_decoder_kedsum_th_free, + + .feed = ws_protocol_decoder_kedsum_th_feed, + .reset = ws_protocol_decoder_kedsum_th_reset, + + .get_hash_data = ws_protocol_decoder_kedsum_th_get_hash_data, + .serialize = ws_protocol_decoder_kedsum_th_serialize, + .deserialize = ws_protocol_decoder_kedsum_th_deserialize, + .get_string = ws_protocol_decoder_kedsum_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_kedsum_th_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_kedsum_th = { + .name = WS_PROTOCOL_KEDSUM_TH_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_kedsum_th_decoder, + .encoder = &ws_protocol_kedsum_th_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_kedsum_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderKedsumTH* instance = malloc(sizeof(WSProtocolDecoderKedsumTH)); + instance->base.protocol = &ws_protocol_kedsum_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_kedsum_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + free(instance); +} + +void ws_protocol_decoder_kedsum_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + instance->decoder.parser_step = KedsumTHDecoderStepReset; +} + +static bool ws_protocol_kedsum_th_check_crc(WSProtocolDecoderKedsumTH* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8, + instance->decoder.decode_data}; + + uint8_t crc = + subghz_protocol_blocks_crc4(msg, 4, 0x03, 0); // CRC-4 poly 0x3 init 0x0 xor last 4 bits + crc ^= msg[4] >> 4; // last nibble is only XORed + return (crc == (msg[4] & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_kedsum_th_remote_controller(WSBlockGeneric* instance) { + instance->id = instance->data >> 32; + if((instance->data >> 30) & 0x3) { + instance->battery_low = 0; + } else { + instance->battery_low = 1; + } + instance->channel = ((instance->data >> 28) & 0x3) + 1; + instance->btn = WS_NO_BTN; + uint16_t temp_raw = ((instance->data >> 16) & 0x0f) << 8 | + ((instance->data >> 20) & 0x0f) << 4 | ((instance->data >> 24) & 0x0f); + instance->temp = locale_fahrenheit_to_celsius(((float)temp_raw - 900.0f) / 10.0f); + instance->humidity = ((instance->data >> 8) & 0x0f) << 4 | ((instance->data >> 12) & 0x0f); +} + +void ws_protocol_decoder_kedsum_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + + switch(instance->decoder.parser_step) { + case KedsumTHDecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta)) { + instance->decoder.parser_step = KedsumTHDecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case KedsumTHDecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 4) < + ws_protocol_kedsum_th_const.te_delta * 4)) { + //Found preambule + instance->header_count++; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (duration < (ws_protocol_kedsum_th_const.te_long * 2 + + ws_protocol_kedsum_th_const.te_delta * 2))) { + //Found syncPrefix + if(instance->header_count > 0) { + instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long) < + ws_protocol_kedsum_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 2) < + ws_protocol_kedsum_th_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KedsumTHDecoderStepReset; + } + } + } else { + instance->decoder.parser_step = KedsumTHDecoderStepReset; + } + } + break; + + case KedsumTHDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = KedsumTHDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = KedsumTHDecoderStepReset; + } + break; + + case KedsumTHDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 4) < + ws_protocol_kedsum_th_const.te_delta * 4) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_kedsum_th_const.min_count_bit_for_found) && + ws_protocol_kedsum_th_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_kedsum_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = KedsumTHDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long) < + ws_protocol_kedsum_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) < + ws_protocol_kedsum_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 2) < + ws_protocol_kedsum_th_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = KedsumTHDecoderStepReset; + } + } else { + instance->decoder.parser_step = KedsumTHDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_kedsum_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_kedsum_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_kedsum_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_kedsum_th_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_kedsum_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderKedsumTH* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/kedsum_th.h b/lib/subghz/protocols/kedsum_th.h new file mode 100644 index 00000000000..6c971cae6f5 --- /dev/null +++ b/lib/subghz/protocols/kedsum_th.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_KEDSUM_TH_NAME "Kedsum-TH" + +typedef struct WSProtocolDecoderKedsumTH WSProtocolDecoderKedsumTH; +typedef struct WSProtocolEncoderKedsumTH WSProtocolEncoderKedsumTH; + +extern const SubGhzProtocolDecoder ws_protocol_kedsum_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_kedsum_th_encoder; +extern const SubGhzProtocol ws_protocol_kedsum_th; + +/** + * Allocate WSProtocolDecoderKedsumTH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderKedsumTH* pointer to a WSProtocolDecoderKedsumTH instance + */ +void* ws_protocol_decoder_kedsum_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderKedsumTH. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + */ +void ws_protocol_decoder_kedsum_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderKedsumTH. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + */ +void ws_protocol_decoder_kedsum_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_kedsum_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_kedsum_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderKedsumTH. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_kedsum_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderKedsumTH. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_kedsum_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderKedsumTH instance + * @param output Resulting text + */ +void ws_protocol_decoder_kedsum_th_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index 84891a61b2b..12388e92ca9 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -1076,10 +1076,10 @@ static void subghz_protocol_keeloq_check_remote_controller( subghz_custom_btn_set_max(4); } -uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_keeloq_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderKeeloq* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/keeloq.h b/lib/subghz/protocols/keeloq.h index 4abd14413be..99349aa5268 100644 --- a/lib/subghz/protocols/keeloq.h +++ b/lib/subghz/protocols/keeloq.h @@ -79,7 +79,7 @@ void subghz_protocol_decoder_keeloq_feed(void* context, bool level, uint32_t dur * @param context Pointer to a SubGhzProtocolDecoderKeeloq instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_keeloq_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderKeeloq. diff --git a/lib/subghz/protocols/kia.c b/lib/subghz/protocols/kia.c index 809c847eaac..cfa9cc3b5c4 100644 --- a/lib/subghz/protocols/kia.c +++ b/lib/subghz/protocols/kia.c @@ -416,10 +416,10 @@ static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* inst subghz_custom_btn_set_max(4); } -uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_kia_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderKIA* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/kia.h b/lib/subghz/protocols/kia.h index ab4082b73f8..35f5507ddfa 100644 --- a/lib/subghz/protocols/kia.h +++ b/lib/subghz/protocols/kia.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t durati * @param context Pointer to a SubGhzProtocolDecoderKIA instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_kia_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderKIA. diff --git a/lib/subghz/protocols/kinggates_stylo_4k.c b/lib/subghz/protocols/kinggates_stylo_4k.c index 9eebe62599f..7665f0ea3ab 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.c +++ b/lib/subghz/protocols/kinggates_stylo_4k.c @@ -501,10 +501,10 @@ static void subghz_protocol_kinggates_stylo_4k_remote_controller( } } -uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderKingGates_stylo_4k* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/kinggates_stylo_4k.h b/lib/subghz/protocols/kinggates_stylo_4k.h index cdefebc8405..26c0aca2711 100644 --- a/lib/subghz/protocols/kinggates_stylo_4k.h +++ b/lib/subghz/protocols/kinggates_stylo_4k.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_kinggates_stylo_4k_feed(void* context, bool level, * @param context Pointer to a SubGhzProtocolDecoderKingGates_stylo_4k instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_kinggates_stylo_4k_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderKingGates_stylo_4k. diff --git a/lib/subghz/protocols/lacrosse_tx.c b/lib/subghz/protocols/lacrosse_tx.c new file mode 100644 index 00000000000..6ed4539a000 --- /dev/null +++ b/lib/subghz/protocols/lacrosse_tx.c @@ -0,0 +1,322 @@ +#include "lacrosse_tx.h" + +#define TAG "WSProtocolLaCrosse_TX" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse.c + * + * + * LaCrosse TX 433 Mhz Temperature and Humidity Sensors. + * - Tested: TX-7U and TX-6U (Temperature only) + * - Not Tested but should work: TX-3, TX-4 + * - also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station) + * - also TFA Dostmann 30.3121 sensor + * Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php + * Message is 44 bits, 11 x 4 bit nybbles: + * [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check] + * Notes: + * - Zero Pulses are longer (1,400 uS High, 1,000 uS Low) = 2,400 uS + * - One Pulses are shorter ( 550 uS High, 1,000 uS Low) = 1,600 uS + * - Sensor id changes when the battery is changed + * - Primary Value are BCD with one decimal place: vvv = 12.3 + * - Secondary value is integer only intval = 12, seems to be a repeat of primary + * This may actually be an additional data check because the 4 bit checksum + * and parity bit is pretty week at detecting errors. + * - Temperature is in Celsius with 50.0 added (to handle negative values) + * - Humidity values appear to be integer precision, decimal always 0. + * - There is a 4 bit checksum and a parity bit covering the three digit value + * - Parity check for TX-3 and TX-4 might be different. + * - Msg sent with one repeat after 30 mS + * - Temperature and humidity are sent as separate messages + * - Frequency for each sensor may be could be off by as much as 50-75 khz + * - LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK + * so they can't be decoded by rtl_433 currently. + * - Temperature and Humidity are sent in different messages bursts. +*/ + +#define LACROSSE_TX_GAP 1000 +#define LACROSSE_TX_BIT_SIZE 44 +#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000 +#define LACROSSE_TX_SUNC_MASK 0x0F000000000 +#define LACROSSE_TX_MSG_TYPE_TEMP 0x00 +#define LACROSSE_TX_MSG_TYPE_HUM 0x0E + +static const SubGhzBlockConst ws_protocol_lacrosse_tx_const = { + .te_short = 550, + .te_long = 1300, + .te_delta = 120, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderLaCrosse_TX { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderLaCrosse_TX { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + LaCrosse_TXDecoderStepReset = 0, + LaCrosse_TXDecoderStepCheckPreambule, + LaCrosse_TXDecoderStepSaveDuration, + LaCrosse_TXDecoderStepCheckDuration, +} LaCrosse_TXDecoderStep; + +const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder = { + .alloc = ws_protocol_decoder_lacrosse_tx_alloc, + .free = ws_protocol_decoder_lacrosse_tx_free, + + .feed = ws_protocol_decoder_lacrosse_tx_feed, + .reset = ws_protocol_decoder_lacrosse_tx_reset, + + .get_hash_data = ws_protocol_decoder_lacrosse_tx_get_hash_data, + .serialize = ws_protocol_decoder_lacrosse_tx_serialize, + .deserialize = ws_protocol_decoder_lacrosse_tx_deserialize, + .get_string = ws_protocol_decoder_lacrosse_tx_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_lacrosse_tx = { + .name = WS_PROTOCOL_LACROSSE_TX_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_lacrosse_tx_decoder, + .encoder = &ws_protocol_lacrosse_tx_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderLaCrosse_TX* instance = malloc(sizeof(WSProtocolDecoderLaCrosse_TX)); + instance->base.protocol = &ws_protocol_lacrosse_tx; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_lacrosse_tx_free(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + free(instance); +} + +void ws_protocol_decoder_lacrosse_tx_reset(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + instance->header_count = 0; + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; +} + +static bool ws_protocol_lacrosse_tx_check_crc(WSProtocolDecoderLaCrosse_TX* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + (instance->decoder.decode_data >> 36) & 0x0F, + (instance->decoder.decode_data >> 32) & 0x0F, + (instance->decoder.decode_data >> 28) & 0x0F, + (instance->decoder.decode_data >> 24) & 0x0F, + (instance->decoder.decode_data >> 20) & 0x0F, + (instance->decoder.decode_data >> 16) & 0x0F, + (instance->decoder.decode_data >> 12) & 0x0F, + (instance->decoder.decode_data >> 8) & 0x0F, + (instance->decoder.decode_data >> 4) & 0x0F}; + + uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9); + return ((crc & 0x0F) == ((instance->decoder.decode_data) & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_lacrosse_tx_remote_controller(WSBlockGeneric* instance) { + uint8_t msg_type = (instance->data >> 32) & 0x0F; + instance->id = (((instance->data >> 28) & 0x0F) << 3) | (((instance->data >> 24) & 0x0F) >> 1); + + float msg_value = (float)((instance->data >> 20) & 0x0F) * 10.0f + + (float)((instance->data >> 16) & 0x0F) + + (float)((instance->data >> 12) & 0x0F) * 0.1f; + + if(msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051 + instance->temp = msg_value - 50.0f; + instance->humidity = WS_NO_HUMIDITY; + } else if(msg_type == LACROSSE_TX_MSG_TYPE_HUM) { + //ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard + instance->humidity = (uint8_t)msg_value; + } else { + furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type."); + } + + instance->btn = WS_NO_BTN; + instance->battery_low = WS_NO_BATT; + instance->channel = WS_NO_CHANNEL; +} + +void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + + switch(instance->decoder.parser_step) { + case LaCrosse_TXDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckPreambule; + instance->header_count = 0; + } + break; + + case LaCrosse_TXDecoderStepCheckPreambule: + + if(level) { + if((DURATION_DIFF(duration, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) && + (instance->header_count > 1)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.te_last = duration; + } else if(duration > (ws_protocol_lacrosse_tx_const.te_long * 2)) { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2) { + instance->decoder.te_last = duration; + instance->header_count++; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + } + + break; + + case LaCrosse_TXDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + break; + + case LaCrosse_TXDecoderStepCheckDuration: + + if(!level) { + if(duration > LACROSSE_TX_GAP * 3) { + if(DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else if( + DURATION_DIFF( + instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < + ws_protocol_lacrosse_tx_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } + if((instance->decoder.decode_data & LACROSSE_TX_SUNC_MASK) == + LACROSSE_TX_SUNC_PATTERN) { + if(ws_protocol_lacrosse_tx_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = LACROSSE_TX_BIT_SIZE; + ws_protocol_lacrosse_tx_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 0; + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) < + ws_protocol_lacrosse_tx_const.te_delta) && + (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) < + ws_protocol_lacrosse_tx_const.te_delta) && + (DURATION_DIFF(duration, LACROSSE_TX_GAP) < + ws_protocol_lacrosse_tx_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + + } else { + instance->decoder.parser_step = LaCrosse_TXDecoderStepReset; + } + + break; + } +} + +uint32_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_lacrosse_tx_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/lacrosse_tx.h b/lib/subghz/protocols/lacrosse_tx.h new file mode 100644 index 00000000000..5a6693b542b --- /dev/null +++ b/lib/subghz/protocols/lacrosse_tx.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_LACROSSE_TX_NAME "LaCrosse_TX" + +typedef struct WSProtocolDecoderLaCrosse_TX WSProtocolDecoderLaCrosse_TX; +typedef struct WSProtocolEncoderLaCrosse_TX WSProtocolEncoderLaCrosse_TX; + +extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder; +extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder; +extern const SubGhzProtocol ws_protocol_lacrosse_tx; + +/** + * Allocate WSProtocolDecoderLaCrosse_TX. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderLaCrosse_TX* pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void ws_protocol_decoder_lacrosse_tx_free(void* context); + +/** + * Reset decoder WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + */ +void ws_protocol_decoder_lacrosse_tx_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderLaCrosse_TX. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance + * @param output Resulting text + */ +void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/lacrosse_tx141thbv2.c b/lib/subghz/protocols/lacrosse_tx141thbv2.c new file mode 100644 index 00000000000..9149f46660b --- /dev/null +++ b/lib/subghz/protocols/lacrosse_tx141thbv2.c @@ -0,0 +1,308 @@ +#include "lacrosse_tx141thbv2.h" + +#define TAG "WSProtocolLaCrosse_TX141THBv2" + +#define LACROSSE_TX141TH_BV2_BIT_COUNT 41 + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse_tx141x.c + * + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u - 41 bit + * or + * iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | -40 bit + * - i: identification; changes on battery switch + * - c: lfsr_digest8_reflect; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - h: Humidity; + * - t: Temperature; in °F as binary number with one decimal place + 50 °F offset + * - n: Channel; Channel number 1 - 3 + */ + +static const SubGhzBlockConst ws_protocol_lacrosse_tx141thbv2_const = { + .te_short = 208, + .te_long = 417, + .te_delta = 120, + .min_count_bit_for_found = 40, +}; + +struct WSProtocolDecoderLaCrosse_TX141THBv2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderLaCrosse_TX141THBv2 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + LaCrosse_TX141THBv2DecoderStepReset = 0, + LaCrosse_TX141THBv2DecoderStepCheckPreambule, + LaCrosse_TX141THBv2DecoderStepSaveDuration, + LaCrosse_TX141THBv2DecoderStepCheckDuration, +} LaCrosse_TX141THBv2DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder = { + .alloc = ws_protocol_decoder_lacrosse_tx141thbv2_alloc, + .free = ws_protocol_decoder_lacrosse_tx141thbv2_free, + + .feed = ws_protocol_decoder_lacrosse_tx141thbv2_feed, + .reset = ws_protocol_decoder_lacrosse_tx141thbv2_reset, + + .get_hash_data = ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data, + .serialize = ws_protocol_decoder_lacrosse_tx141thbv2_serialize, + .deserialize = ws_protocol_decoder_lacrosse_tx141thbv2_deserialize, + .get_string = ws_protocol_decoder_lacrosse_tx141thbv2_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2 = { + .name = WS_PROTOCOL_LACROSSE_TX141THBV2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_lacrosse_tx141thbv2_decoder, + .encoder = &ws_protocol_lacrosse_tx141thbv2_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = + malloc(sizeof(WSProtocolDecoderLaCrosse_TX141THBv2)); + instance->base.protocol = &ws_protocol_lacrosse_tx141thbv2; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + free(instance); +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; +} + +static bool + ws_protocol_lacrosse_tx141thbv2_check_crc(WSProtocolDecoderLaCrosse_TX141THBv2* instance) { + if(!instance->decoder.decode_data) return false; + uint64_t data = instance->decoder.decode_data; + if(instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + uint8_t msg[] = {data >> 32, data >> 24, data >> 16, data >> 8}; + + uint8_t crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4); + return (crc == (data & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) { + uint64_t data = instance->data; + if(instance->data_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT) { + data >>= 1; + } + instance->id = data >> 32; + instance->battery_low = (data >> 31) & 1; + instance->btn = (data >> 30) & 1; + instance->channel = ((data >> 28) & 0x03) + 1; + instance->temp = ((float)((data >> 16) & 0x0FFF) - 500.0f) / 10.0f; + instance->humidity = (data >> 8) & 0xFF; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static bool ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + WSProtocolDecoderLaCrosse_TX141THBv2* instance, + uint32_t te_last, + uint32_t te_current) { + furi_assert(instance); + bool ret = false; + if(DURATION_DIFF( + te_last + te_current, + ws_protocol_lacrosse_tx141thbv2_const.te_short + + ws_protocol_lacrosse_tx141thbv2_const.te_long) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) { + if(te_last > te_current) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } + ret = true; + } + + return ret; +} +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + + switch(instance->decoder.parser_step) { + case LaCrosse_TX141THBv2DecoderStepReset: + if((level) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if(instance->header_count == 4) { + if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { + instance->decoder.decode_data = instance->decoder.decode_data & 1; + instance->decoder.decode_count_bit = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } + break; + + case LaCrosse_TX141THBv2DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + + case LaCrosse_TX141THBv2DecoderStepCheckDuration: + if(!level) { + if(((DURATION_DIFF( + instance->decoder.te_last, + ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 4) < + ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) { + if((instance->decoder.decode_count_bit == + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) || + (instance->decoder.decode_count_bit == LACROSSE_TX141TH_BV2_BIT_COUNT)) { + if(ws_protocol_lacrosse_tx141thbv2_check_crc(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_lacrosse_tx141thbv2_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->header_count = 1; + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepCheckPreambule; + break; + } + } else if(ws_protocol_decoder_lacrosse_tx141thbv2_add_bit( + instance, instance->decoder.te_last, duration)) { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + } else { + instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->generic.data_count_bit != + ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found && + instance->generic.data_count_bit != LACROSSE_TX141TH_BV2_BIT_COUNT) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} + +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderLaCrosse_TX141THBv2* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/lacrosse_tx141thbv2.h b/lib/subghz/protocols/lacrosse_tx141thbv2.h new file mode 100644 index 00000000000..642ed5f11c3 --- /dev/null +++ b/lib/subghz/protocols/lacrosse_tx141thbv2.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_LACROSSE_TX141THBV2_NAME "TX141THBv2" + +typedef struct WSProtocolDecoderLaCrosse_TX141THBv2 WSProtocolDecoderLaCrosse_TX141THBv2; +typedef struct WSProtocolEncoderLaCrosse_TX141THBv2 WSProtocolEncoderLaCrosse_TX141THBv2; + +extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx141thbv2_decoder; +extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx141thbv2_encoder; +extern const SubGhzProtocol ws_protocol_lacrosse_tx141thbv2; + +/** + * Allocate WSProtocolDecoderLaCrosse_TX141THBv2. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderLaCrosse_TX141THBv2* pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void* ws_protocol_decoder_lacrosse_tx141thbv2_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_free(void* context); + +/** + * Reset decoder WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_lacrosse_tx141thbv2_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderLaCrosse_TX141THBv2. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_lacrosse_tx141thbv2_deserialize( + void* context, + FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderLaCrosse_TX141THBv2 instance + * @param output Resulting text + */ +void ws_protocol_decoder_lacrosse_tx141thbv2_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/linear.c b/lib/subghz/protocols/linear.c index 8d37357966b..b4c162c353d 100644 --- a/lib/subghz/protocols/linear.c +++ b/lib/subghz/protocols/linear.c @@ -293,10 +293,10 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur } } -uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_linear_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderLinear* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/linear.h b/lib/subghz/protocols/linear.h index b692b911c1e..a94ab4578b0 100644 --- a/lib/subghz/protocols/linear.h +++ b/lib/subghz/protocols/linear.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_linear_feed(void* context, bool level, uint32_t dur * @param context Pointer to a SubGhzProtocolDecoderLinear instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_linear_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_linear_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderLinear. diff --git a/lib/subghz/protocols/linear_delta3.c b/lib/subghz/protocols/linear_delta3.c index 97ac5cc5a65..f8171cf16f9 100644 --- a/lib/subghz/protocols/linear_delta3.c +++ b/lib/subghz/protocols/linear_delta3.c @@ -304,10 +304,10 @@ void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint3 } } -uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderLinearDelta3* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8)); } diff --git a/lib/subghz/protocols/linear_delta3.h b/lib/subghz/protocols/linear_delta3.h index 22f6730f441..c16abaeed03 100644 --- a/lib/subghz/protocols/linear_delta3.h +++ b/lib/subghz/protocols/linear_delta3.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_linear_delta3_feed(void* context, bool level, uint3 * @param context Pointer to a SubGhzProtocolDecoderLinearDelta3 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_linear_delta3_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderLinearDelta3. diff --git a/lib/subghz/protocols/magellan.c b/lib/subghz/protocols/magellan.c index a8c7f63429a..d57d2fdfc1f 100644 --- a/lib/subghz/protocols/magellan.c +++ b/lib/subghz/protocols/magellan.c @@ -64,11 +64,12 @@ const SubGhzProtocol subghz_protocol_magellan = { .name = SUBGHZ_PROTOCOL_MAGELLAN_NAME, .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | - SubGhzProtocolFlag_Magellan, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_magellan_decoder, .encoder = &subghz_protocol_magellan_encoder, + + .filter = SubGhzProtocolFilter_Magellan, }; void* subghz_protocol_encoder_magellan_alloc(SubGhzEnvironment* environment) { @@ -391,10 +392,10 @@ static void subghz_protocol_magellan_get_event_serialize(uint8_t event, FuriStri ((event >> 7) & 0x1 ? ", ?" : "")); } -uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_magellan_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderMagellan* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/magellan.h b/lib/subghz/protocols/magellan.h index e0fb7ca52a0..3e4a87c6513 100644 --- a/lib/subghz/protocols/magellan.h +++ b/lib/subghz/protocols/magellan.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_magellan_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderMagellan instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_magellan_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_magellan_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderMagellan. diff --git a/lib/subghz/protocols/marantec.c b/lib/subghz/protocols/marantec.c index fc4aa0dca4e..82981ceb5d9 100644 --- a/lib/subghz/protocols/marantec.c +++ b/lib/subghz/protocols/marantec.c @@ -336,10 +336,10 @@ void subghz_protocol_decoder_marantec_feed(void* context, bool level, volatile u } } -uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_marantec_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderMarantec* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/marantec.h b/lib/subghz/protocols/marantec.h index 485c563b2fb..6b77d051123 100644 --- a/lib/subghz/protocols/marantec.h +++ b/lib/subghz/protocols/marantec.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_marantec_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderMarantec instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_marantec_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_marantec_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderMarantec. diff --git a/lib/subghz/protocols/mastercode.c b/lib/subghz/protocols/mastercode.c index 54ad5bfaa44..c8f7eb3336d 100644 --- a/lib/subghz/protocols/mastercode.c +++ b/lib/subghz/protocols/mastercode.c @@ -313,10 +313,10 @@ static void subghz_protocol_mastercode_check_remote_controller(SubGhzBlockGeneri instance->btn = (instance->data >> 2 & 0x03); } -uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_mastercode_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderMastercode* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/mastercode.h b/lib/subghz/protocols/mastercode.h index c5c73db989a..6ad914a57e3 100644 --- a/lib/subghz/protocols/mastercode.h +++ b/lib/subghz/protocols/mastercode.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_mastercode_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderMastercode instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_mastercode_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_mastercode_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderMastercode. diff --git a/lib/subghz/protocols/megacode.c b/lib/subghz/protocols/megacode.c index ba58bc445b7..aae6c7db02a 100644 --- a/lib/subghz/protocols/megacode.c +++ b/lib/subghz/protocols/megacode.c @@ -374,10 +374,10 @@ static void subghz_protocol_megacode_check_remote_controller(SubGhzBlockGeneric* } } -uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_megacode_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderMegaCode* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/megacode.h b/lib/subghz/protocols/megacode.h index 616ecdf64ef..1a7b5062170 100644 --- a/lib/subghz/protocols/megacode.h +++ b/lib/subghz/protocols/megacode.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_megacode_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderMegaCode instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_megacode_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_megacode_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderMegaCode. diff --git a/lib/subghz/protocols/nero_radio.c b/lib/subghz/protocols/nero_radio.c index 7e787ffd0bc..9f356a2394e 100644 --- a/lib/subghz/protocols/nero_radio.c +++ b/lib/subghz/protocols/nero_radio.c @@ -350,10 +350,10 @@ void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderNeroRadio* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/nero_radio.h b/lib/subghz/protocols/nero_radio.h index 0598aee6ccd..793044cd640 100644 --- a/lib/subghz/protocols/nero_radio.h +++ b/lib/subghz/protocols/nero_radio.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_nero_radio_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderNeroRadio instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_nero_radio_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderNeroRadio. diff --git a/lib/subghz/protocols/nero_sketch.c b/lib/subghz/protocols/nero_sketch.c index 09cd0255ae9..484d142b3ba 100644 --- a/lib/subghz/protocols/nero_sketch.c +++ b/lib/subghz/protocols/nero_sketch.c @@ -321,10 +321,10 @@ void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_ } } -uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderNeroSketch* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/nero_sketch.h b/lib/subghz/protocols/nero_sketch.h index b557772d20d..47a779570ac 100644 --- a/lib/subghz/protocols/nero_sketch.h +++ b/lib/subghz/protocols/nero_sketch.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_nero_sketch_feed(void* context, bool level, uint32_ * @param context Pointer to a SubGhzProtocolDecoderNeroSketch instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_nero_sketch_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderNeroSketch. diff --git a/lib/subghz/protocols/nexus_th.c b/lib/subghz/protocols/nexus_th.c new file mode 100644 index 00000000000..fea7e6132b1 --- /dev/null +++ b/lib/subghz/protocols/nexus_th.c @@ -0,0 +1,366 @@ +#include "nexus_th.h" + +#define TAG "WSProtocolNexus_TH" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/nexus.c + * + * Nexus sensor protocol with ID, temperature and optional humidity + * also FreeTec (Pearl) NC-7345 sensors for FreeTec Weatherstation NC-7344, + * also infactory/FreeTec (Pearl) NX-3980 sensors for infactory/FreeTec NX-3974 station, + * also Solight TE82S sensors for Solight TE76/TE82/TE83/TE84 stations, + * also TFA 30.3209.02 temperature/humidity sensor. + * The sensor sends 36 bits 12 times, + * the packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~1000 us for a 0 bit or a long ~2000 us gap for a + * 1 bit, the sync gap is ~4000 us. + * The data is grouped in 9 nibbles: + * [id0] [id1] [flags] [temp0] [temp1] [temp2] [const] [humi0] [humi1] + * - The 8-bit id changes when the battery is changed in the sensor. + * - flags are 4 bits B 0 C C, where B is the battery status: 1=OK, 0=LOW + * - and CC is the channel: 0=CH1, 1=CH2, 2=CH3 + * - temp is 12 bit signed scaled by 10 + * - const is always 1111 (0x0F) + * - humidity is 8 bits + * The sensors can be bought at Clas Ohlsen (Nexus) and Pearl (infactory/FreeTec). + * + * Generate test files: https://htotoo.github.io/FlipperSUBGenerator/nexus-th-generator/index.html + */ + +#define NEXUS_TH_CONST_DATA 0b1111 + +static const SubGhzBlockConst ws_protocol_nexus_th_const = { + .te_short = 490, + .te_long = 1980, + .te_delta = 150, + .min_count_bit_for_found = 36, +}; + +struct WSProtocolDecoderNexus_TH { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderNexus_TH { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Nexus_THDecoderStepReset = 0, + Nexus_THDecoderStepSaveDuration, + Nexus_THDecoderStepCheckDuration, +} Nexus_THDecoderStep; + +void* ws_protocol_encoder_nexus_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolEncoderNexus_TH* instance = malloc(sizeof(WSProtocolEncoderNexus_TH)); + + instance->base.protocol = &ws_protocol_nexus_th; + instance->generic.protocol_name = instance->base.protocol->name; + + instance->encoder.repeat = 12; + instance->encoder.size_upload = ws_protocol_nexus_th_const.min_count_bit_for_found * 2 + 2; + instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration)); + instance->encoder.is_running = false; + return instance; +} + +void ws_protocol_encoder_nexus_th_free(void* context) { + furi_assert(context); + WSProtocolEncoderNexus_TH* instance = context; + free(instance->encoder.upload); + free(instance); +} + +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderNexus_TH* instance = malloc(sizeof(WSProtocolDecoderNexus_TH)); + instance->base.protocol = &ws_protocol_nexus_th; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_nexus_th_free(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + free(instance); +} + +void ws_protocol_decoder_nexus_th_reset(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + instance->decoder.parser_step = Nexus_THDecoderStepReset; +} + +static bool ws_protocol_nexus_th_check(WSProtocolDecoderNexus_TH* instance) { + uint8_t type = (instance->decoder.decode_data >> 8) & 0x0F; + + if((type == NEXUS_TH_CONST_DATA) && ((instance->decoder.decode_data >> 4) != 0xffffffff)) { + return true; + } else { + return false; + } + return true; +} + +static bool ws_protocol_encoder_nexus_th_get_upload(WSProtocolEncoderNexus_TH* instance) { + furi_assert(instance); + size_t index = 0; + size_t size_upload = (instance->generic.data_count_bit * 2) + 2; + if(size_upload > instance->encoder.size_upload) { + FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer."); + return false; + } else { + instance->encoder.size_upload = size_upload; + } + + for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) { + if(!bit_read(instance->generic.data, i - 1)) { + //send bit 1 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_nexus_th_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_nexus_th_const.te_short * 2); + } else { + //send bit 0 + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_nexus_th_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_nexus_th_const.te_short * 4); + } + } + + instance->encoder.upload[index++] = + level_duration_make(true, (uint32_t)ws_protocol_nexus_th_const.te_short); + instance->encoder.upload[index++] = + level_duration_make(false, (uint32_t)ws_protocol_nexus_th_const.te_short * 8); + + return true; +} + +SubGhzProtocolStatus + ws_protocol_encoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolEncoderNexus_TH* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if((instance->generic.data_count_bit > + ws_protocol_nexus_th_const.min_count_bit_for_found + 1)) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + //optional parameter parameter + flipper_format_read_uint32( + flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 12); + + if(!ws_protocol_encoder_nexus_th_get_upload(instance)) { + ret = SubGhzProtocolStatusErrorEncoderGetUpload; + break; + } + instance->encoder.is_running = true; + } while(false); + + return ret; +} + +LevelDuration ws_protocol_encoder_nexus_th_yield(void* context) { + WSProtocolEncoderNexus_TH* 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; +} + +void ws_protocol_encoder_nexus_th_stop(void* context) { + WSProtocolEncoderNexus_TH* instance = context; + instance->encoder.is_running = false; +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 28) & 0xFF; + instance->battery_low = !((instance->data >> 27) & 1); + instance->channel = ((instance->data >> 24) & 0x03) + 1; + instance->btn = WS_NO_BTN; + if(!((instance->data >> 23) & 1)) { + instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 12) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = instance->data & 0xFF; + if(instance->humidity > 95) + instance->humidity = 95; + else if(instance->humidity < 20) + instance->humidity = 20; +} + +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + + switch(instance->decoder.parser_step) { + case Nexus_THDecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4)) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case Nexus_THDecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + + case Nexus_THDecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 8) < + ws_protocol_nexus_th_const.te_delta * 4) { + //Found sync + instance->decoder.parser_step = Nexus_THDecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_nexus_th_const.min_count_bit_for_found) && + ws_protocol_nexus_th_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_nexus_th_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = Nexus_THDecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 2) < + ws_protocol_nexus_th_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_nexus_th_const.te_short) < + ws_protocol_nexus_th_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_nexus_th_const.te_short * 4) < + ws_protocol_nexus_th_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Nexus_THDecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + } else { + instance->decoder.parser_step = Nexus_THDecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_nexus_th_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_nexus_th_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderNexus_TH* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} + +const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder = { + .alloc = ws_protocol_decoder_nexus_th_alloc, + .free = ws_protocol_decoder_nexus_th_free, + + .feed = ws_protocol_decoder_nexus_th_feed, + .reset = ws_protocol_decoder_nexus_th_reset, + + .get_hash_data = ws_protocol_decoder_nexus_th_get_hash_data, + .serialize = ws_protocol_decoder_nexus_th_serialize, + .deserialize = ws_protocol_decoder_nexus_th_deserialize, + .get_string = ws_protocol_decoder_nexus_th_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder = { + .alloc = ws_protocol_encoder_nexus_th_alloc, + .free = ws_protocol_encoder_nexus_th_free, + + .deserialize = ws_protocol_encoder_nexus_th_deserialize, + .stop = ws_protocol_encoder_nexus_th_stop, + .yield = ws_protocol_encoder_nexus_th_yield, +}; + +const SubGhzProtocol ws_protocol_nexus_th = { + .name = WS_PROTOCOL_NEXUS_TH_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, + + .decoder = &ws_protocol_nexus_th_decoder, + .encoder = &ws_protocol_nexus_th_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; diff --git a/lib/subghz/protocols/nexus_th.h b/lib/subghz/protocols/nexus_th.h new file mode 100644 index 00000000000..cd8db219917 --- /dev/null +++ b/lib/subghz/protocols/nexus_th.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_NEXUS_TH_NAME "Nexus-TH" + +typedef struct WSProtocolDecoderNexus_TH WSProtocolDecoderNexus_TH; +typedef struct WSProtocolEncoderNexus_TH WSProtocolEncoderNexus_TH; + +extern const SubGhzProtocolDecoder ws_protocol_nexus_th_decoder; +extern const SubGhzProtocolEncoder ws_protocol_nexus_th_encoder; +extern const SubGhzProtocol ws_protocol_nexus_th; + +/** + * Allocate WSProtocolDecoderNexus_TH. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderNexus_TH* pointer to a WSProtocolDecoderNexus_TH instance + */ +void* ws_protocol_decoder_nexus_th_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_free(void* context); + +/** + * Reset decoder WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + */ +void ws_protocol_decoder_nexus_th_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_nexus_th_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_nexus_th_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_nexus_th_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderNexus_TH. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_nexus_th_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderNexus_TH instance + * @param output Resulting text + */ +void ws_protocol_decoder_nexus_th_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/nice_flo.c b/lib/subghz/protocols/nice_flo.c index f60e07fb846..31f234dd1af 100644 --- a/lib/subghz/protocols/nice_flo.c +++ b/lib/subghz/protocols/nice_flo.c @@ -276,10 +276,10 @@ void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t d } } -uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderNiceFlo* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/nice_flo.h b/lib/subghz/protocols/nice_flo.h index 9a4b53d1271..d4ec5cfda45 100644 --- a/lib/subghz/protocols/nice_flo.h +++ b/lib/subghz/protocols/nice_flo.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_nice_flo_feed(void* context, bool level, uint32_t d * @param context Pointer to a SubGhzProtocolDecoderNiceFlo instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_nice_flo_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderNiceFlo. diff --git a/lib/subghz/protocols/nice_flor_s.c b/lib/subghz/protocols/nice_flor_s.c index 3f56ba10789..cc447d68524 100644 --- a/lib/subghz/protocols/nice_flor_s.c +++ b/lib/subghz/protocols/nice_flor_s.c @@ -80,10 +80,12 @@ const SubGhzProtocol subghz_protocol_nice_flor_s = { .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | - SubGhzProtocolFlag_Send | SubGhzProtocolFlag_NiceFlorS, + SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_nice_flor_s_decoder, .encoder = &subghz_protocol_nice_flor_s_encoder, + + .filter = SubGhzProtocolFilter_NiceFlorS, }; static void subghz_protocol_nice_flor_s_remote_controller( @@ -691,10 +693,10 @@ static void subghz_protocol_nice_flor_s_remote_controller( subghz_custom_btn_set_max(4); } -uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderNiceFlorS* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/nice_flor_s.h b/lib/subghz/protocols/nice_flor_s.h index 4d635d3fbd3..df1f5eef458 100644 --- a/lib/subghz/protocols/nice_flor_s.h +++ b/lib/subghz/protocols/nice_flor_s.h @@ -80,7 +80,7 @@ void subghz_protocol_decoder_nice_flor_s_feed(void* context, bool level, uint32_ * @param context Pointer to a SubGhzProtocolDecoderNiceFlorS instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_nice_flor_s_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderNiceFlorS. diff --git a/lib/subghz/protocols/oregon2.c b/lib/subghz/protocols/oregon2.c new file mode 100644 index 00000000000..01b462af1f8 --- /dev/null +++ b/lib/subghz/protocols/oregon2.c @@ -0,0 +1,432 @@ +#include "oregon2.h" + +#include +#include +#include +#include +#include "ws_generic.h" + +#include +#include + +#define TAG "WSProtocolOregon2" + +static const SubGhzBlockConst ws_oregon2_const = { + .te_long = 1000, + .te_short = 500, + .te_delta = 200, + .min_count_bit_for_found = 32, +}; + +#define OREGON2_PREAMBLE_BITS 19 +#define OREGON2_PREAMBLE_MASK 0b1111111111111111111 +#define OREGON2_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) +#define OREGON2_CHECKSUM_BITS 8 + +// 15 ones + 0101 (inverted A) +#define OREGON2_PREAMBLE 0b1111111111111110101 + +// bit indicating the low battery +#define OREGON2_FLAG_BAT_LOW 0x4 + +/// Documentation for Oregon Scientific protocols can be found here: +/// http://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf +// Sensors ID +#define ID_THGR122N 0x1d20 +#define ID_THGR968 0x1d30 +#define ID_BTHR918 0x5d50 +#define ID_BHTR968 0x5d60 +#define ID_RGR968 0x2d10 +#define ID_THR228N 0xec40 +#define ID_THN132N 0xec40 // same as THR228N but different packet size +#define ID_RTGN318 0x0cc3 // warning: id is from 0x0cc3 and 0xfcc3 +#define ID_RTGN129 0x0cc3 // same as RTGN318 but different packet size +#define ID_THGR810 0xf824 // This might be ID_THGR81, but what's true is lost in (git) history +#define ID_THGR810a 0xf8b4 // unconfirmed version +#define ID_THN802 0xc844 +#define ID_PCR800 0x2914 +#define ID_PCR800a 0x2d14 // Different PCR800 ID - AU version I think +#define ID_WGR800 0x1984 +#define ID_WGR800a 0x1994 // unconfirmed version +#define ID_WGR968 0x3d00 +#define ID_UV800 0xd874 +#define ID_THN129 0xcc43 // THN129 Temp only +#define ID_RTHN129 0x0cd3 // RTHN129 Temp, clock sensors +#define ID_RTHN129_1 0x9cd3 +#define ID_RTHN129_2 0xacd3 +#define ID_RTHN129_3 0xbcd3 +#define ID_RTHN129_4 0xccd3 +#define ID_RTHN129_5 0xdcd3 +#define ID_BTHGN129 0x5d53 // Baro, Temp, Hygro sensor +#define ID_UVR128 0xec70 +#define ID_THGR328N 0xcc23 // Temp & Hygro sensor similar to THR228N with 5 channel instead of 3 +#define ID_RTGR328N_1 0xdcc3 // RTGR328N_[1-5] RFclock(date &time)&Temp&Hygro sensor +#define ID_RTGR328N_2 0xccc3 +#define ID_RTGR328N_3 0xbcc3 +#define ID_RTGR328N_4 0xacc3 +#define ID_RTGR328N_5 0x9cc3 +#define ID_RTGR328N_6 0x8ce3 // RTGR328N_6&7 RFclock(date &time)&Temp&Hygro sensor like THGR328N +#define ID_RTGR328N_7 0x8ae3 + +struct WSProtocolDecoderOregon2 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + bool prev_bit; + bool have_bit; + + uint8_t var_bits; + uint32_t var_data; +}; + +typedef struct WSProtocolDecoderOregon2 WSProtocolDecoderOregon2; + +typedef enum { + Oregon2DecoderStepReset = 0, + Oregon2DecoderStepFoundPreamble, + Oregon2DecoderStepVarData, +} Oregon2DecoderStep; + +void* ws_protocol_decoder_oregon2_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon2* instance = malloc(sizeof(WSProtocolDecoderOregon2)); + instance->base.protocol = &ws_protocol_oregon2; + instance->generic.protocol_name = instance->base.protocol->name; + instance->generic.humidity = WS_NO_HUMIDITY; + instance->generic.temp = WS_NO_TEMPERATURE; + instance->generic.btn = WS_NO_BTN; + instance->generic.channel = WS_NO_CHANNEL; + instance->generic.battery_low = WS_NO_BATT; + instance->generic.id = WS_NO_ID; + return instance; +} + +void ws_protocol_decoder_oregon2_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon2_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->have_bit = false; + instance->var_data = 0; + instance->var_bits = 0; +} + +static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { + bool is_long = false; + + if(DURATION_DIFF(duration, ws_oregon2_const.te_long) < ws_oregon2_const.te_delta) { + is_long = true; + } else if(DURATION_DIFF(duration, ws_oregon2_const.te_short) < ws_oregon2_const.te_delta) { + is_long = false; + } else { + return ManchesterEventReset; + } + + if(level) + return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; + else + return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; +} + +// From sensor id code return amount of bits in variable section +// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific +static uint8_t oregon2_sensor_id_var_bits(uint16_t sensor_id) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + return 16; + case ID_THGR122N: + return 24; + default: + return 0; + } +} + +static void ws_oregon2_decode_const_data(WSBlockGeneric* ws_block) { + ws_block->id = OREGON2_SENSOR_ID(ws_block->data); + + uint8_t ch_bits = (ws_block->data >> 12) & 0xF; + ws_block->channel = 1; + while(ch_bits > 1) { + ws_block->channel++; + ch_bits >>= 1; + } + + ws_block->battery_low = (ws_block->data & OREGON2_FLAG_BAT_LOW) ? 1 : 0; +} + +uint16_t bcd_decode_short(uint32_t data) { + return (data & 0xF) * 10 + ((data >> 4) & 0xF); +} + +static float ws_oregon2_decode_temp(uint32_t data) { + int32_t temp_val; + temp_val = bcd_decode_short(data >> 4); + temp_val *= 10; + temp_val += (data >> 12) & 0xF; + if(data & 0xF) temp_val = -temp_val; + return (float)temp_val / 10.0; +} + +static void ws_oregon2_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { + switch(sensor_id) { + case ID_THR228N: + case ID_RTHN129_1: + case ID_RTHN129_2: + case ID_RTHN129_3: + case ID_RTHN129_4: + case ID_RTHN129_5: + ws_b->temp = ws_oregon2_decode_temp(data); + ws_b->humidity = WS_NO_HUMIDITY; + return; + case ID_THGR122N: + ws_b->humidity = bcd_decode_short(data); + ws_b->temp = ws_oregon2_decode_temp(data >> 8); + return; + default: + break; + } +} + +void ws_protocol_decoder_oregon2_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + // oregon v2.1 signal is inverted + ManchesterEvent event = level_and_duration_to_event(!level, duration); + bool data; + + // low-level bit sequence decoding + if(event == ManchesterEventReset) { + instance->decoder.parser_step = Oregon2DecoderStepReset; + instance->have_bit = false; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + } + if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data)) { + if(instance->have_bit) { + if(!instance->prev_bit && data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + } else if(instance->prev_bit && !data) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + } else { + ws_protocol_decoder_oregon2_reset(context); + } + instance->have_bit = false; + } else { + instance->prev_bit = data; + instance->have_bit = true; + } + } + + switch(instance->decoder.parser_step) { + case Oregon2DecoderStepReset: + // waiting for fixed oregon2 preamble + if(instance->decoder.decode_count_bit >= OREGON2_PREAMBLE_BITS && + ((instance->decoder.decode_data & OREGON2_PREAMBLE_MASK) == OREGON2_PREAMBLE)) { + instance->decoder.parser_step = Oregon2DecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case Oregon2DecoderStepFoundPreamble: + // waiting for fixed oregon2 data + if(instance->decoder.decode_count_bit == 32) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + + // reverse nibbles in decoded data + instance->generic.data = (instance->generic.data & 0x55555555) << 1 | + (instance->generic.data & 0xAAAAAAAA) >> 1; + instance->generic.data = (instance->generic.data & 0x33333333) << 2 | + (instance->generic.data & 0xCCCCCCCC) >> 2; + + ws_oregon2_decode_const_data(&instance->generic); + instance->var_bits = + oregon2_sensor_id_var_bits(OREGON2_SENSOR_ID(instance->generic.data)); + + if(!instance->var_bits) { + // sensor is not supported, stop decoding, but showing the decoded fixed part + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } else { + instance->decoder.parser_step = Oregon2DecoderStepVarData; + } + } + break; + case Oregon2DecoderStepVarData: + // waiting for variable (sensor-specific data) + if(instance->decoder.decode_count_bit == instance->var_bits + OREGON2_CHECKSUM_BITS) { + instance->var_data = instance->decoder.decode_data & 0xFFFFFFFF; + + // reverse nibbles in var data + instance->var_data = (instance->var_data & 0x55555555) << 1 | + (instance->var_data & 0xAAAAAAAA) >> 1; + instance->var_data = (instance->var_data & 0x33333333) << 2 | + (instance->var_data & 0xCCCCCCCC) >> 2; + + ws_oregon2_decode_var_data( + &instance->generic, + OREGON2_SENSOR_ID(instance->generic.data), + instance->var_data >> OREGON2_CHECKSUM_BITS); + + instance->decoder.parser_step = Oregon2DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } +} + +uint32_t ws_protocol_decoder_oregon2_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_oregon2_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret != SubGhzProtocolStatusOk) return ret; + uint32_t temp = instance->var_bits; + if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { + FURI_LOG_E(TAG, "Error adding VarBits"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_hex( + flipper_format, + "VarData", + (const uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Error adding VarData"); + return SubGhzProtocolStatusErrorParserOthers; + } + return ret; +} + +SubGhzProtocolStatus + ws_protocol_decoder_oregon2_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + uint32_t temp_data; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing VarLen"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->var_bits = (uint8_t)temp_data; + if(!flipper_format_read_hex( + flipper_format, + "VarData", + (uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { //-V1051 + FURI_LOG_E(TAG, "Missing VarData"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(instance->generic.data_count_bit != ws_oregon2_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} + +static void oregon2_append_check_sum(uint32_t fix_data, uint32_t var_data, FuriString* output) { + uint8_t sum = fix_data & 0xF; + uint8_t ref_sum = var_data & 0xFF; + var_data >>= 8; + + for(uint8_t i = 1; i < 8; i++) { + fix_data >>= 4; + var_data >>= 4; + sum += (fix_data & 0xF) + (var_data & 0xF); + } + + // swap calculated sum nibbles + sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; + if(sum == ref_sum) + furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + else + furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); +} + +void ws_protocol_decoder_oregon2_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon2* instance = context; + furi_string_cat_printf( + output, + "%s\r\n" + "ID: 0x%04lX, ch: %d,\r\nbat: %d, rc: 0x%02lX\r\n", + instance->generic.protocol_name, + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (uint32_t)(instance->generic.data >> 4) & 0xFF); + + if(instance->var_bits > 0) { + furi_string_cat_printf( + output, + "Temp:%d.%d C Hum:%d%%", + (int16_t)instance->generic.temp, + abs( + ((int16_t)(instance->generic.temp * 10) - + (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); + oregon2_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); + } +} + +const SubGhzProtocolDecoder ws_protocol_oregon2_decoder = { + .alloc = ws_protocol_decoder_oregon2_alloc, + .free = ws_protocol_decoder_oregon2_free, + + .feed = ws_protocol_decoder_oregon2_feed, + .reset = ws_protocol_decoder_oregon2_reset, + + .get_hash_data = ws_protocol_decoder_oregon2_get_hash_data, + .serialize = ws_protocol_decoder_oregon2_serialize, + .deserialize = ws_protocol_decoder_oregon2_deserialize, + .get_string = ws_protocol_decoder_oregon2_get_string, +}; + +const SubGhzProtocol ws_protocol_oregon2 = { + .name = WS_PROTOCOL_OREGON2_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_oregon2_decoder, + + .filter = SubGhzProtocolFilter_Weather, +}; diff --git a/lib/subghz/protocols/oregon2.h b/lib/subghz/protocols/oregon2.h new file mode 100644 index 00000000000..cfe938e6d83 --- /dev/null +++ b/lib/subghz/protocols/oregon2.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +#define WS_PROTOCOL_OREGON2_NAME "Oregon2" +extern const SubGhzProtocol ws_protocol_oregon2; diff --git a/lib/subghz/protocols/oregon3.c b/lib/subghz/protocols/oregon3.c new file mode 100644 index 00000000000..75c2d60d670 --- /dev/null +++ b/lib/subghz/protocols/oregon3.c @@ -0,0 +1,368 @@ +#include "oregon3.h" + +#include +#include +#include +#include +#include "ws_generic.h" + +#include +#include + +#define TAG "WSProtocolOregon3" + +static const SubGhzBlockConst ws_oregon3_const = { + .te_long = 1100, + .te_short = 500, + .te_delta = 300, + .min_count_bit_for_found = 32, +}; + +#define OREGON3_PREAMBLE_BITS 28 +#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111 +// 24 ones + 0101 (inverted A) +#define OREGON3_PREAMBLE 0b1111111111111111111111110101 + +// Fixed part contains: +// - Sensor type: 16 bits +// - Channel: 4 bits +// - ID (changes when batteries are changed): 8 bits +// - Battery status: 4 bits +#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4) +#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF) +#define OREGON3_CHECKSUM_BITS 8 + +// bit indicating the low battery +#define OREGON3_FLAG_BAT_LOW 0x4 + +/// Documentation for Oregon Scientific protocols can be found here: +/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf +// Sensors ID +#define ID_THGR221 0xf824 + +struct WSProtocolDecoderOregon3 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + bool prev_bit; + + uint8_t var_bits; + uint64_t var_data; +}; + +typedef struct WSProtocolDecoderOregon3 WSProtocolDecoderOregon3; + +typedef enum { + Oregon3DecoderStepReset = 0, + Oregon3DecoderStepFoundPreamble, + Oregon3DecoderStepVarData, +} Oregon3DecoderStep; + +void* ws_protocol_decoder_oregon3_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon3* instance = malloc(sizeof(WSProtocolDecoderOregon3)); + instance->base.protocol = &ws_protocol_oregon3; + instance->generic.protocol_name = instance->base.protocol->name; + instance->generic.humidity = WS_NO_HUMIDITY; + instance->generic.temp = WS_NO_TEMPERATURE; + instance->generic.btn = WS_NO_BTN; + instance->generic.channel = WS_NO_CHANNEL; + instance->generic.battery_low = WS_NO_BATT; + instance->generic.id = WS_NO_ID; + instance->prev_bit = false; + return instance; +} + +void ws_protocol_decoder_oregon3_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon3_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + instance->decoder.parser_step = Oregon3DecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL); + instance->prev_bit = false; + instance->var_data = 0; + instance->var_bits = 0; +} + +static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) { + bool is_long = false; + + if(DURATION_DIFF(duration, ws_oregon3_const.te_long) < ws_oregon3_const.te_delta) { + is_long = true; + } else if(DURATION_DIFF(duration, ws_oregon3_const.te_short) < ws_oregon3_const.te_delta) { + is_long = false; + } else { + return ManchesterEventReset; + } + + if(level) + return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh; + else + return is_long ? ManchesterEventLongLow : ManchesterEventShortLow; +} + +// From sensor id code return amount of bits in variable section +// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific +static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) { + switch(sensor_id) { + case ID_THGR221: + // nibbles: temp + hum + '0' + return (4 + 2 + 1) * 4; + default: + FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id); + return 0; + } +} + +static void ws_oregon3_decode_const_data(WSBlockGeneric* ws_block) { + ws_block->id = OREGON3_SENSOR_ID(ws_block->data); + ws_block->channel = (ws_block->data >> 12) & 0xF; + ws_block->battery_low = (ws_block->data & OREGON3_FLAG_BAT_LOW) ? 1 : 0; +} + +static uint16_t ws_oregon3_bcd_decode_short(uint32_t data) { + return (data & 0xF) * 10 + ((data >> 4) & 0xF); +} + +static float ws_oregon3_decode_temp(uint32_t data) { + int32_t temp_val; + temp_val = ws_oregon3_bcd_decode_short(data >> 4); + temp_val *= 10; + temp_val += (data >> 12) & 0xF; + if(data & 0xF) temp_val = -temp_val; + return (float)temp_val / 10.0; +} + +static void ws_oregon3_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) { + switch(sensor_id) { + case ID_THGR221: + default: + ws_b->humidity = ws_oregon3_bcd_decode_short(data >> 4); + ws_b->temp = ws_oregon3_decode_temp(data >> 12); + break; + } +} + +void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + // Oregon v3.0 protocol is inverted + ManchesterEvent event = level_and_duration_to_event(!level, duration); + + // low-level bit sequence decoding + if(event == ManchesterEventReset) { + instance->decoder.parser_step = Oregon3DecoderStepReset; + instance->prev_bit = false; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + } + if(manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &instance->prev_bit)) { + subghz_protocol_blocks_add_bit(&instance->decoder, instance->prev_bit); + } + + switch(instance->decoder.parser_step) { + case Oregon3DecoderStepReset: + // waiting for fixed oregon3 preamble + if(instance->decoder.decode_count_bit >= OREGON3_PREAMBLE_BITS && + ((instance->decoder.decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) { + instance->decoder.parser_step = Oregon3DecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case Oregon3DecoderStepFoundPreamble: + // waiting for fixed oregon3 data + if(instance->decoder.decode_count_bit == OREGON3_FIXED_PART_BITS) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + + // reverse nibbles in decoded data as oregon v3.0 is LSB first + instance->generic.data = (instance->generic.data & 0x55555555) << 1 | + (instance->generic.data & 0xAAAAAAAA) >> 1; + instance->generic.data = (instance->generic.data & 0x33333333) << 2 | + (instance->generic.data & 0xCCCCCCCC) >> 2; + + ws_oregon3_decode_const_data(&instance->generic); + instance->var_bits = + oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data)); + + if(!instance->var_bits) { + // sensor is not supported, stop decoding + instance->decoder.parser_step = Oregon3DecoderStepReset; + } else { + instance->decoder.parser_step = Oregon3DecoderStepVarData; + } + } + break; + case Oregon3DecoderStepVarData: + // waiting for variable (sensor-specific data) + if(instance->decoder.decode_count_bit == instance->var_bits + OREGON3_CHECKSUM_BITS) { + instance->var_data = instance->decoder.decode_data & 0xFFFFFFFFFFFFFFFF; + + // reverse nibbles in var data + instance->var_data = (instance->var_data & 0x5555555555555555) << 1 | + (instance->var_data & 0xAAAAAAAAAAAAAAAA) >> 1; + instance->var_data = (instance->var_data & 0x3333333333333333) << 2 | + (instance->var_data & 0xCCCCCCCCCCCCCCCC) >> 2; + + ws_oregon3_decode_var_data( + &instance->generic, + OREGON3_SENSOR_ID(instance->generic.data), + instance->var_data >> OREGON3_CHECKSUM_BITS); + + instance->decoder.parser_step = Oregon3DecoderStepReset; + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + break; + } +} + +uint32_t ws_protocol_decoder_oregon3_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_oregon3_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset); + if(ret != SubGhzProtocolStatusOk) return ret; + uint32_t temp = instance->var_bits; + if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) { + FURI_LOG_E(TAG, "Error adding VarBits"); + return SubGhzProtocolStatusErrorParserOthers; + } + if(!flipper_format_write_hex( + flipper_format, + "VarData", + (const uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { + FURI_LOG_E(TAG, "Error adding VarData"); + return SubGhzProtocolStatusErrorParserOthers; + } + return ret; +} + +SubGhzProtocolStatus + ws_protocol_decoder_oregon3_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + uint32_t temp_data; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(&instance->generic, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing VarLen"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->var_bits = (uint8_t)temp_data; + if(!flipper_format_read_hex( + flipper_format, + "VarData", + (uint8_t*)&instance->var_data, + sizeof(instance->var_data))) { //-V1051 + FURI_LOG_E(TAG, "Missing VarData"); + ret = SubGhzProtocolStatusErrorParserOthers; + break; + } + if(instance->generic.data_count_bit != ws_oregon3_const.min_count_bit_for_found) { + FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} + +static void oregon3_append_check_sum(uint32_t fix_data, uint64_t var_data, FuriString* output) { + uint8_t sum = fix_data & 0xF; + uint8_t ref_sum = var_data & 0xFF; + var_data >>= 4; + + for(uint8_t i = 1; i < 8; i++) { + fix_data >>= 4; + var_data >>= 4; + sum += (fix_data & 0xF) + (var_data & 0xF); + } + + // swap calculated sum nibbles + sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF; + if(sum == ref_sum) + furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum); + else + furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum); +} + +void ws_protocol_decoder_oregon3_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon3* instance = context; + furi_string_cat_printf( + output, + "%s\r\n" + "ID: 0x%04lX, ch: %d,\r\nbat: %d, rc: 0x%02lX\r\n", + instance->generic.protocol_name, + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (uint32_t)(instance->generic.data >> 4) & 0xFF); + + if(instance->var_bits > 0) { + furi_string_cat_printf( + output, + "Temp:%d.%d C Hum:%d%%", + (int16_t)instance->generic.temp, + abs( + ((int16_t)(instance->generic.temp * 10) - + (((int16_t)instance->generic.temp) * 10))), + instance->generic.humidity); + oregon3_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output); + } +} + +const SubGhzProtocolDecoder ws_protocol_oregon3_decoder = { + .alloc = ws_protocol_decoder_oregon3_alloc, + .free = ws_protocol_decoder_oregon3_free, + + .feed = ws_protocol_decoder_oregon3_feed, + .reset = ws_protocol_decoder_oregon3_reset, + + .get_hash_data = ws_protocol_decoder_oregon3_get_hash_data, + .serialize = ws_protocol_decoder_oregon3_serialize, + .deserialize = ws_protocol_decoder_oregon3_deserialize, + .get_string = ws_protocol_decoder_oregon3_get_string, +}; + +const SubGhzProtocol ws_protocol_oregon3 = { + .name = WS_PROTOCOL_OREGON3_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_oregon3_decoder, + + .filter = SubGhzProtocolFilter_Weather, +}; diff --git a/lib/subghz/protocols/oregon3.h b/lib/subghz/protocols/oregon3.h new file mode 100644 index 00000000000..ec51ddb0001 --- /dev/null +++ b/lib/subghz/protocols/oregon3.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +#define WS_PROTOCOL_OREGON3_NAME "Oregon3" +extern const SubGhzProtocol ws_protocol_oregon3; diff --git a/lib/subghz/protocols/oregon_v1.c b/lib/subghz/protocols/oregon_v1.c new file mode 100644 index 00000000000..bced94a7dc4 --- /dev/null +++ b/lib/subghz/protocols/oregon_v1.c @@ -0,0 +1,324 @@ +#include "oregon_v1.h" +#include + +#define TAG "WSProtocolOregon_V1" + +/* + * Help + * https://github.dev/merbanan/rtl_433/blob/bb1be7f186ac0fdb7dc5d77693847d96fb95281e/src/devices/oregon_scientific_v1.c + * + * OSv1 protocol. + * + * MC with nominal bit width of 2930 us. + * Pulses are somewhat longer than nominal half-bit width, 1748 us / 3216 us, + * Gaps are somewhat shorter than nominal half-bit width, 1176 us / 2640 us. + * After 12 preamble bits there is 4200 us gap, 5780 us pulse, 5200 us gap. + * And next 32 bit data + * + * Care must be taken with the gap after the sync pulse since it + * is outside of the normal clocking. Because of this a data stream + * beginning with a 0 will have data in this gap. + * + * + * Data is in reverse order of bits + * RevBit(data32bit)=> tib23atad + * + * tib23atad => xxxxxxxx | busuTTTT | ttttzzzz | ccuuiiii + * + * - i: ID + * - x: CRC; + * - u: unknown; + * - b: battery low; flag to indicate low battery voltage + * - s: temperature sign + * - T: BCD, Temperature; in �C * 10 + * - t: BCD, Temperature; in �C * 1 + * - z: BCD, Temperature; in �C * 0.1 + * - c: Channel 00=CH1, 01=CH2, 10=CH3 + * + */ + +#define OREGON_V1_HEADER_OK 0xFF + +static const SubGhzBlockConst ws_protocol_oregon_v1_const = { + .te_short = 1465, + .te_long = 2930, + .te_delta = 350, + .min_count_bit_for_found = 32, +}; + +struct WSProtocolDecoderOregon_V1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + ManchesterState manchester_state; + uint16_t header_count; + uint8_t first_bit; +}; + +struct WSProtocolEncoderOregon_V1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Oregon_V1DecoderStepReset = 0, + Oregon_V1DecoderStepFoundPreamble, + Oregon_V1DecoderStepParse, +} Oregon_V1DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder = { + .alloc = ws_protocol_decoder_oregon_v1_alloc, + .free = ws_protocol_decoder_oregon_v1_free, + + .feed = ws_protocol_decoder_oregon_v1_feed, + .reset = ws_protocol_decoder_oregon_v1_reset, + + .get_hash_data = ws_protocol_decoder_oregon_v1_get_hash_data, + .serialize = ws_protocol_decoder_oregon_v1_serialize, + .deserialize = ws_protocol_decoder_oregon_v1_deserialize, + .get_string = ws_protocol_decoder_oregon_v1_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_oregon_v1 = { + .name = WS_PROTOCOL_OREGON_V1_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_oregon_v1_decoder, + .encoder = &ws_protocol_oregon_v1_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderOregon_V1* instance = malloc(sizeof(WSProtocolDecoderOregon_V1)); + instance->base.protocol = &ws_protocol_oregon_v1; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_oregon_v1_free(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + free(instance); +} + +void ws_protocol_decoder_oregon_v1_reset(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + instance->decoder.parser_step = Oregon_V1DecoderStepReset; +} + +static bool ws_protocol_oregon_v1_check(WSProtocolDecoderOregon_V1* instance) { + if(!instance->decoder.decode_data) return false; + uint64_t data = subghz_protocol_blocks_reverse_key(instance->decoder.decode_data, 32); + uint16_t crc = (data & 0xff) + ((data >> 8) & 0xff) + ((data >> 16) & 0xff); + crc = (crc & 0xff) + ((crc >> 8) & 0xff); + return (crc == ((data >> 24) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_oregon_v1_remote_controller(WSBlockGeneric* instance) { + uint64_t data = subghz_protocol_blocks_reverse_key(instance->data, 32); + + instance->id = data & 0xFF; + instance->channel = ((data >> 6) & 0x03) + 1; + + float temp_raw = + ((data >> 8) & 0x0F) * 0.1f + ((data >> 12) & 0x0F) + ((data >> 16) & 0x0F) * 10.0f; + if(!((data >> 21) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } + + instance->battery_low = !((instance->data >> 23) & 1ULL); + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + ManchesterEvent event = ManchesterEventReset; + switch(instance->decoder.parser_step) { + case Oregon_V1DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.parser_step = Oregon_V1DecoderStepFoundPreamble; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + case Oregon_V1DecoderStepFoundPreamble: + if(level) { + //keep high levels, if they suit our durations + if((DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + instance->decoder.te_last = duration; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else if( + //checking low levels + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // Found header + instance->header_count++; + } else if( + (DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short * 3) < + ws_protocol_oregon_v1_const.te_delta) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta)) { + // check header + if(instance->header_count > 7) { + instance->header_count = OREGON_V1_HEADER_OK; + } + } else if( + (instance->header_count == OREGON_V1_HEADER_OK) && + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_oregon_v1_const.te_short * 4) < + ws_protocol_oregon_v1_const.te_delta)) { + //found all the necessary patterns + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + instance->decoder.parser_step = Oregon_V1DecoderStepParse; + if(duration < ws_protocol_oregon_v1_const.te_short * 4) { + instance->first_bit = 1; + } else { + instance->first_bit = 0; + } + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + break; + case Oregon_V1DecoderStepParse: + if(level) { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortHigh; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongHigh; + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } else { + if(DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_short) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventShortLow; + } else if( + DURATION_DIFF(duration, ws_protocol_oregon_v1_const.te_long) < + ws_protocol_oregon_v1_const.te_delta) { + event = ManchesterEventLongLow; + } else if(duration >= ((uint32_t)ws_protocol_oregon_v1_const.te_long * 2)) { + if(instance->decoder.decode_count_bit == + ws_protocol_oregon_v1_const.min_count_bit_for_found) { + if(instance->first_bit) { + instance->decoder.decode_data = ~instance->decoder.decode_data | (1 << 31); + } + if(ws_protocol_oregon_v1_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_oregon_v1_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + manchester_advance( + instance->manchester_state, + ManchesterEventReset, + &instance->manchester_state, + NULL); + } else { + instance->decoder.parser_step = Oregon_V1DecoderStepReset; + } + } + if(event != ManchesterEventReset) { + bool data; + bool data_ok = manchester_advance( + instance->manchester_state, event, &instance->manchester_state, &data); + + if(data_ok) { + instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data; + instance->decoder.decode_count_bit++; + } + } + + break; + } +} + +uint32_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_oregon_v1_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderOregon_V1* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/oregon_v1.h b/lib/subghz/protocols/oregon_v1.h new file mode 100644 index 00000000000..95887cc9df2 --- /dev/null +++ b/lib/subghz/protocols/oregon_v1.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_OREGON_V1_NAME "Oregon-v1" + +typedef struct WSProtocolDecoderOregon_V1 WSProtocolDecoderOregon_V1; +typedef struct WSProtocolEncoderOregon_V1 WSProtocolEncoderOregon_V1; + +extern const SubGhzProtocolDecoder ws_protocol_oregon_v1_decoder; +extern const SubGhzProtocolEncoder ws_protocol_oregon_v1_encoder; +extern const SubGhzProtocol ws_protocol_oregon_v1; + +/** + * Allocate WSProtocolDecoderOregon_V1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderOregon_V1* pointer to a WSProtocolDecoderOregon_V1 instance + */ +void* ws_protocol_decoder_oregon_v1_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_free(void* context); + +/** + * Reset decoder WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + */ +void ws_protocol_decoder_oregon_v1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_oregon_v1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_oregon_v1_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_oregon_v1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderOregon_V1. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_oregon_v1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderOregon_V1 instance + * @param output Resulting text + */ +void ws_protocol_decoder_oregon_v1_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/pcsg_generic.c b/lib/subghz/protocols/pcsg_generic.c new file mode 100644 index 00000000000..fc56049a2ac --- /dev/null +++ b/lib/subghz/protocols/pcsg_generic.c @@ -0,0 +1,133 @@ +#include "pcsg_generic.h" +#include +#include + +#define TAG "PCSGBlockGeneric" + +void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +SubGhzProtocolStatus pcsg_block_generic_serialize( + PCSGBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, PCSG_KEY_FILE_TYPE, PCSG_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + break; + } + + pcsg_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + break; + } + } + if(!flipper_format_write_float(flipper_format, "Latitute", &preset->latitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Latitute"); + res = SubGhzProtocolStatusErrorParserLatitude; + break; + } + if(!flipper_format_write_float(flipper_format, "Longitude", &preset->longitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Longitude"); + res = SubGhzProtocolStatusErrorParserLongitude; + break; + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + break; + } + + if(!flipper_format_write_string(flipper_format, "Ric", instance->result_ric)) { + FURI_LOG_E(TAG, "Unable to add Ric"); + break; + } + + if(!flipper_format_write_string(flipper_format, "Message", instance->result_msg)) { + FURI_LOG_E(TAG, "Unable to add Message"); + break; + } + + res = SubGhzProtocolStatusOk; + } while(false); + furi_string_free(temp_str); + return res; +} + +SubGhzProtocolStatus + pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + FuriString* temp_data = furi_string_alloc(); + FuriString* temp_data2 = furi_string_alloc(); + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + break; + } + + if(!flipper_format_read_string(flipper_format, "Ric", temp_data2)) { + FURI_LOG_E(TAG, "Missing Ric"); + break; + } + if(instance->result_ric != NULL) { + furi_string_set(instance->result_ric, temp_data2); + } else { + instance->result_ric = furi_string_alloc_set(temp_data2); + } + + if(!flipper_format_read_string(flipper_format, "Message", temp_data)) { + FURI_LOG_E(TAG, "Missing Message"); + break; + } + if(instance->result_msg != NULL) { + furi_string_set(instance->result_msg, temp_data); + } else { + instance->result_msg = furi_string_alloc_set(temp_data); + } + + res = SubGhzProtocolStatusOk; + } while(0); + + furi_string_free(temp_data); + furi_string_free(temp_data2); + + return res; +} diff --git a/lib/subghz/protocols/pcsg_generic.h b/lib/subghz/protocols/pcsg_generic.h new file mode 100644 index 00000000000..3578c1d15df --- /dev/null +++ b/lib/subghz/protocols/pcsg_generic.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include "furi_hal.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define PCSG_KEY_FILE_VERSION 1 +#define PCSG_KEY_FILE_TYPE "Flipper SubGhz Key File" + +typedef struct PCSGBlockGeneric PCSGBlockGeneric; + +struct PCSGBlockGeneric { + const char* protocol_name; + FuriString* result_ric; + FuriString* result_msg; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void pcsg_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return true On success + */ +SubGhzProtocolStatus pcsg_block_generic_serialize( + PCSGBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data PCSGBlockGeneric. + * @param instance Pointer to a PCSGBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return true On success + */ +SubGhzProtocolStatus + pcsg_block_generic_deserialize(PCSGBlockGeneric* instance, FlipperFormat* flipper_format); + +float pcsg_block_generic_fahrenheit_to_celsius(float fahrenheit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/protocols/phoenix_v2.c b/lib/subghz/protocols/phoenix_v2.c index 2416a9d0109..e1e2b9acc89 100644 --- a/lib/subghz/protocols/phoenix_v2.c +++ b/lib/subghz/protocols/phoenix_v2.c @@ -286,10 +286,10 @@ static void subghz_protocol_phoenix_v2_check_remote_controller(SubGhzBlockGeneri instance->btn = (data_rev >> 32) & 0xF; } -uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderPhoenix_V2* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/phoenix_v2.h b/lib/subghz/protocols/phoenix_v2.h index 0724de1f095..9a1f6475291 100644 --- a/lib/subghz/protocols/phoenix_v2.h +++ b/lib/subghz/protocols/phoenix_v2.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_phoenix_v2_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderPhoenix_V2 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_phoenix_v2_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderPhoenix_V2. diff --git a/lib/subghz/protocols/pocsag.c b/lib/subghz/protocols/pocsag.c new file mode 100644 index 00000000000..6cd7e67a244 --- /dev/null +++ b/lib/subghz/protocols/pocsag.c @@ -0,0 +1,445 @@ +#include "pocsag.h" + +#include +#include +#include + +#define TAG "POCSAG" + +static const SubGhzBlockConst pocsag_const = { + .te_short = 833, + .te_delta = 100, +}; +static const SubGhzBlockConst pocsag512_const = { + .te_short = 1950, + .te_long = 1950, + .te_delta = 120, +}; +static const SubGhzBlockConst pocsag2400_const = { + .te_short = 410, + .te_long = 410, + .te_delta = 60, +}; + +// Minimal amount of sync bits (interleaving zeros and ones) +#define POCSAG_MIN_SYNC_BITS 24 +#define POCSAG_CW_BITS 32 +#define POCSAG_CW_MASK 0xFFFFFFFF +#define POCSAG_FRAME_SYNC_CODE 0x7CD215D8 +#define POCSAG_IDLE_CODE_WORD 0x7A89C197 + +#define POCSAG_FUNC_NUM 0 +#define POCSAG_FUNC_ALERT1 1 +#define POCSAG_FUNC_ALERT2 2 +#define POCSAG_FUNC_ALPHANUM 3 + +static const char* func_msg[] = {"\e#Num:\e# ", "\e#Alert\e#", "\e#Alert:\e# ", "\e#Msg:\e# "}; +static const char* bcd_chars = "*U -)("; + +struct SubGhzProtocolDecoderPocsag { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + PCSGBlockGeneric generic; + + uint8_t codeword_idx; + uint32_t ric; + uint8_t func; + + // partially decoded character + uint8_t char_bits; + uint8_t char_data; + + // message being decoded + FuriString* msg; + + // Done messages, ready to be serialized/deserialized + FuriString* done_msg; + + SubGhzBlockConst* pocsag_timing; + uint32_t version; +}; + +typedef struct SubGhzProtocolDecoderPocsag SubGhzProtocolDecoderPocsag; + +typedef enum { + PocsagDecoderStepReset = 0, + PocsagDecoderStepFoundSync, + PocsagDecoderStepFoundPreamble, + PocsagDecoderStepMessage, +} PocsagDecoderStep; + +void* subghz_protocol_decoder_pocsag_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + + SubGhzProtocolDecoderPocsag* instance = malloc(sizeof(SubGhzProtocolDecoderPocsag)); + instance->base.protocol = &subghz_protocol_pocsag; + instance->generic.protocol_name = instance->base.protocol->name; + instance->msg = furi_string_alloc(); + instance->done_msg = furi_string_alloc(); + instance->pocsag_timing = NULL; //not synced yet + if(instance->generic.result_msg == NULL) { + instance->generic.result_msg = furi_string_alloc(); + } + if(instance->generic.result_ric == NULL) { + instance->generic.result_ric = furi_string_alloc(); + } + + return instance; +} + +void subghz_protocol_decoder_pocsag_free(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_free(instance->msg); + furi_string_free(instance->done_msg); + furi_string_free(instance->generic.result_msg); + furi_string_free(instance->generic.result_ric); + free(instance); +} + +void subghz_protocol_decoder_pocsag_reset(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + instance->decoder.parser_step = PocsagDecoderStepReset; + instance->decoder.decode_data = 0UL; + instance->decoder.decode_count_bit = 0; + instance->codeword_idx = 0; + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); + furi_string_reset(instance->done_msg); + furi_string_reset(instance->generic.result_msg); + furi_string_reset(instance->generic.result_ric); +} + +static void pocsag_decode_address_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + instance->ric = (data >> 13); + instance->ric = (instance->ric << 3) | (instance->codeword_idx >> 1); + instance->func = (data >> 11) & 0b11; +} + +static bool decode_message_alphanumeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + for(uint8_t i = 0; i < 20; i++) { + instance->char_data >>= 1; + if(data & (1 << 30)) { + instance->char_data |= 1 << 6; + } + instance->char_bits++; + if(instance->char_bits == 7) { + if(instance->char_data == 0) return false; + furi_string_push_back(instance->msg, instance->char_data); + instance->char_data = 0; + instance->char_bits = 0; + } + data <<= 1; + } + return true; +} + +static void decode_message_numeric(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + // 5 groups with 4 bits each + uint8_t val; + for(uint8_t i = 0; i < 5; i++) { + val = (data >> (27 - i * 4)) & 0b1111; + // reverse the order of 4 bits + val = (val & 0x5) << 1 | (val & 0xA) >> 1; + val = (val & 0x3) << 2 | (val & 0xC) >> 2; + + if(val <= 9) + val += '0'; + else + val = bcd_chars[val - 10]; + furi_string_push_back(instance->msg, val); + } +} + +// decode message word, maintaining instance state for partial decoding. Return true if more data +// might follow or false if end of message reached. +static bool pocsag_decode_message_word(SubGhzProtocolDecoderPocsag* instance, uint32_t data) { + switch(instance->func) { + case POCSAG_FUNC_ALERT2: + case POCSAG_FUNC_ALPHANUM: + return decode_message_alphanumeric(instance, data); + + case POCSAG_FUNC_NUM: + decode_message_numeric(instance, data); + return true; + } + return false; +} + +// Function called when current message got decoded, but other messages might follow +static void pocsag_message_done(SubGhzProtocolDecoderPocsag* instance) { + // append the message to the long-term storage string + furi_string_printf(instance->generic.result_ric, "\e#RIC: %" PRIu32 "\e# | ", instance->ric); + furi_string_cat_str(instance->generic.result_ric, func_msg[instance->func]); + if(instance->func != POCSAG_FUNC_ALERT1) { + furi_string_cat(instance->done_msg, instance->msg); + } + furi_string_cat_str(instance->done_msg, " "); + + furi_string_cat(instance->generic.result_msg, instance->done_msg); + + // reset the state + instance->char_bits = 0; + instance->char_data = 0; + furi_string_reset(instance->msg); +} + +void subghz_protocol_decoder_pocsag_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + + // reset state - waiting for 32 bits of interleaving 1s and 0s + if(instance->decoder.parser_step == PocsagDecoderStepReset) { + if(DURATION_DIFF(duration, pocsag_const.te_short) < pocsag_const.te_delta) { + if(instance->pocsag_timing != &pocsag_const) { + //timing changed, so reset before, and override + subghz_protocol_decoder_pocsag_reset(context); + instance->pocsag_timing = (SubGhzBlockConst*)&pocsag_const; + instance->version = 1200; + } + // POCSAG signals are inverted + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) { + instance->decoder.parser_step = PocsagDecoderStepFoundSync; + } + } else if(DURATION_DIFF(duration, pocsag512_const.te_short) < pocsag512_const.te_delta) { + if(instance->pocsag_timing != &pocsag512_const) { + //timing changed, so reset before, and override + subghz_protocol_decoder_pocsag_reset(context); + instance->pocsag_timing = (SubGhzBlockConst*)&pocsag512_const; + instance->version = 512; + } + // POCSAG signals are inverted + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) { + instance->decoder.parser_step = PocsagDecoderStepFoundSync; + } + } else if(DURATION_DIFF(duration, pocsag2400_const.te_short) < pocsag2400_const.te_delta) { + if(instance->pocsag_timing != &pocsag2400_const) { + //timing changed, so reset before, and override + subghz_protocol_decoder_pocsag_reset(context); + instance->pocsag_timing = (SubGhzBlockConst*)&pocsag2400_const; + instance->version = 2400; + } + // POCSAG signals are inverted + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + if(instance->decoder.decode_count_bit == POCSAG_MIN_SYNC_BITS) { + instance->decoder.parser_step = PocsagDecoderStepFoundSync; + } + } else if(instance->decoder.decode_count_bit > 0) { + subghz_protocol_decoder_pocsag_reset(context); + } + return; + } + + int bits_count = duration / instance->pocsag_timing->te_short; + uint32_t extra = duration - instance->pocsag_timing->te_short * bits_count; + + if(DURATION_DIFF(extra, instance->pocsag_timing->te_short) < instance->pocsag_timing->te_delta) + bits_count++; + else if(extra > instance->pocsag_timing->te_delta) { + // in non-reset state we faced the error signal - we reached the end of the packet, flush data + if(furi_string_size(instance->done_msg) > 0) { + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + subghz_protocol_decoder_pocsag_reset(context); + return; + } + + uint32_t codeword; + + // handle state machine for every incoming bit + while(bits_count-- > 0) { + subghz_protocol_blocks_add_bit(&instance->decoder, !level); + + switch(instance->decoder.parser_step) { + case PocsagDecoderStepFoundSync: + if((instance->decoder.decode_data & POCSAG_CW_MASK) == POCSAG_FRAME_SYNC_CODE) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + case PocsagDecoderStepFoundPreamble: + // handle codewords + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + instance->codeword_idx++; + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // Here we expect only address messages + if(codeword >> 31 == 0) { + pocsag_decode_address_word(instance, codeword); + instance->decoder.parser_step = PocsagDecoderStepMessage; + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + + case PocsagDecoderStepMessage: + if(instance->decoder.decode_count_bit == POCSAG_CW_BITS) { + codeword = (uint32_t)(instance->decoder.decode_data & POCSAG_CW_MASK); + switch(codeword) { + case POCSAG_IDLE_CODE_WORD: + // Idle during the message stops the message + instance->codeword_idx++; + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + break; + case POCSAG_FRAME_SYNC_CODE: + instance->codeword_idx = 0; + break; + default: + // In this state, both address and message words can arrive + if(codeword >> 31 == 0) { + pocsag_message_done(instance); + pocsag_decode_address_word(instance, codeword); + } else { + if(!pocsag_decode_message_word(instance, codeword)) { + instance->decoder.parser_step = PocsagDecoderStepFoundPreamble; + pocsag_message_done(instance); + } + } + instance->codeword_idx++; + } + instance->decoder.decode_count_bit = 0; + instance->decoder.decode_data = 0UL; + } + break; + } + } +} + +uint32_t subghz_protocol_decoder_pocsag_get_hash_data(void* context) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + union { + uint32_t full; + uint8_t split[4]; + } hash = {0}; + size_t len = furi_string_size(instance->done_msg); + for(size_t i = 0; i < len; i++) + hash.split[i % sizeof(hash)] ^= furi_string_get_char(instance->done_msg, i); + return hash.full; +} + +SubGhzProtocolStatus subghz_protocol_decoder_pocsag_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + uint32_t msg_len; + + if(SubGhzProtocolStatusOk != + pcsg_block_generic_serialize(&instance->generic, flipper_format, preset)) + return SubGhzProtocolStatusError; + + msg_len = furi_string_size(instance->done_msg); + if(!flipper_format_write_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Error adding MsgLen"); + return SubGhzProtocolStatusError; + } + + if(!flipper_format_write_uint32(flipper_format, "PocsagVer", &instance->version, 1)) { + FURI_LOG_E(TAG, "Error adding PocsagVer"); + return SubGhzProtocolStatusError; + } + + uint8_t* s = (uint8_t*)furi_string_get_cstr(instance->done_msg); + if(!flipper_format_write_hex(flipper_format, "Msg", s, msg_len)) { + FURI_LOG_E(TAG, "Error adding Msg"); + return SubGhzProtocolStatusError; + } + return SubGhzProtocolStatusOk; +} + +SubGhzProtocolStatus + subghz_protocol_decoder_pocsag_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + uint32_t msg_len; + uint8_t* buf; + + do { + if(SubGhzProtocolStatusOk != + pcsg_block_generic_deserialize(&instance->generic, flipper_format)) { + break; + } + + if(!flipper_format_read_uint32(flipper_format, "MsgLen", &msg_len, 1)) { + FURI_LOG_E(TAG, "Missing MsgLen"); + break; + } + //optional, so compatible backwards + instance->version = 1200; + flipper_format_read_uint32(flipper_format, "PocsagVer", &instance->version, 1); + + buf = malloc(msg_len); + if(!flipper_format_read_hex(flipper_format, "Msg", buf, msg_len)) { + FURI_LOG_E(TAG, "Missing Msg"); + free(buf); + break; + } + furi_string_set_strn(instance->done_msg, (const char*)buf, msg_len); + free(buf); + + ret = SubGhzProtocolStatusOk; + } while(false); + return ret; +} + +void subhz_protocol_decoder_pocsag_get_string(void* context, FuriString* output) { + furi_assert(context); + SubGhzProtocolDecoderPocsag* instance = context; + furi_string_cat_printf( + output, "%s %lu\r\n", instance->generic.protocol_name, instance->version); + furi_string_cat_printf(output, "Addr: %lu\r\n", instance->ric); + furi_string_cat(output, instance->done_msg); +} + +const SubGhzProtocolDecoder subghz_protocol_pocsag_decoder = { + .alloc = subghz_protocol_decoder_pocsag_alloc, + .free = subghz_protocol_decoder_pocsag_free, + .reset = subghz_protocol_decoder_pocsag_reset, + .feed = subghz_protocol_decoder_pocsag_feed, + .get_hash_data = subghz_protocol_decoder_pocsag_get_hash_data, + .serialize = subghz_protocol_decoder_pocsag_serialize, + .deserialize = subghz_protocol_decoder_pocsag_deserialize, + .get_string = subhz_protocol_decoder_pocsag_get_string, +}; + +const SubGhzProtocolEncoder subghz_protocol_pocsag_encoder = { + .alloc = NULL, + .free = NULL, + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol subghz_protocol_pocsag = { + .name = SUBGHZ_PROTOCOL_POCSAG_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Save | + SubGhzProtocolFlag_Load, + + .decoder = &subghz_protocol_pocsag_decoder, + .encoder = &subghz_protocol_pocsag_encoder, +}; diff --git a/lib/subghz/protocols/pocsag.h b/lib/subghz/protocols/pocsag.h new file mode 100644 index 00000000000..559fa391848 --- /dev/null +++ b/lib/subghz/protocols/pocsag.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include +#include +#include +#include "pcsg_generic.h" +#include + +#define SUBGHZ_PROTOCOL_POCSAG_NAME "POCSAG" + +extern const SubGhzProtocol subghz_protocol_pocsag; diff --git a/lib/subghz/protocols/power_smart.c b/lib/subghz/protocols/power_smart.c index d03282f73a1..b75d3d8e742 100644 --- a/lib/subghz/protocols/power_smart.c +++ b/lib/subghz/protocols/power_smart.c @@ -335,10 +335,10 @@ static const char* subghz_protocol_power_smart_get_name_button(uint8_t btn) { return name_btn[btn]; } -uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_power_smart_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderPowerSmart* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/power_smart.h b/lib/subghz/protocols/power_smart.h index 5687cf8b1ba..9b9deee0177 100644 --- a/lib/subghz/protocols/power_smart.h +++ b/lib/subghz/protocols/power_smart.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_power_smart_feed(void* context, bool level, uint32_ * @param context Pointer to a SubGhzProtocolDecoderPowerSmart instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_power_smart_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_power_smart_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderPowerSmart. diff --git a/lib/subghz/protocols/princeton.c b/lib/subghz/protocols/princeton.c index 1a7fe53edd8..f8eee62e3fd 100644 --- a/lib/subghz/protocols/princeton.c +++ b/lib/subghz/protocols/princeton.c @@ -73,10 +73,12 @@ const SubGhzProtocol subghz_protocol_princeton = { .type = SubGhzProtocolTypeStatic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | SubGhzProtocolFlag_Princeton, + SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_princeton_decoder, .encoder = &subghz_protocol_princeton_encoder, + + .filter = SubGhzProtocolFilter_Princeton, }; void* subghz_protocol_encoder_princeton_alloc(SubGhzEnvironment* environment) { @@ -303,10 +305,10 @@ static void subghz_protocol_princeton_check_remote_controller(SubGhzBlockGeneric instance->btn = instance->data & 0xF; } -uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_princeton_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderPrinceton* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/princeton.h b/lib/subghz/protocols/princeton.h index 7c775292d56..3c15bb0058a 100644 --- a/lib/subghz/protocols/princeton.h +++ b/lib/subghz/protocols/princeton.h @@ -82,7 +82,7 @@ void subghz_protocol_decoder_princeton_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderPrinceton instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_princeton_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_princeton_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderPrinceton. diff --git a/lib/subghz/protocols/protocol_items.c b/lib/subghz/protocols/protocol_items.c index 5a44a5a4e86..c76ab67628f 100644 --- a/lib/subghz/protocols/protocol_items.c +++ b/lib/subghz/protocols/protocol_items.c @@ -1,29 +1,74 @@ #include "protocol_items.h" const SubGhzProtocol* subghz_protocol_registry_items[] = { - &subghz_protocol_gate_tx, &subghz_protocol_keeloq, - &subghz_protocol_star_line, &subghz_protocol_nice_flo, - &subghz_protocol_came, &subghz_protocol_faac_slh, - &subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, - &subghz_protocol_came_atomo, &subghz_protocol_nero_sketch, - &subghz_protocol_ido, &subghz_protocol_kia, - &subghz_protocol_hormann, &subghz_protocol_nero_radio, - &subghz_protocol_somfy_telis, &subghz_protocol_somfy_keytis, - &subghz_protocol_scher_khan, &subghz_protocol_princeton, - &subghz_protocol_raw, &subghz_protocol_linear, - &subghz_protocol_secplus_v2, &subghz_protocol_secplus_v1, - &subghz_protocol_megacode, &subghz_protocol_holtek, - &subghz_protocol_chamb_code, &subghz_protocol_power_smart, - &subghz_protocol_marantec, &subghz_protocol_bett, - &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2, - &subghz_protocol_honeywell, &subghz_protocol_honeywell_wdb, - &subghz_protocol_magellan, &subghz_protocol_intertechno_v3, - &subghz_protocol_clemsa, &subghz_protocol_ansonic, - &subghz_protocol_smc5326, &subghz_protocol_holtek_th12x, - &subghz_protocol_linear_delta3, &subghz_protocol_dooya, - &subghz_protocol_alutech_at_4n, &subghz_protocol_kinggates_stylo_4k, - &subghz_protocol_bin_raw, &subghz_protocol_x10, - &subghz_protocol_genie, &subghz_protocol_mastercode, + &subghz_protocol_gate_tx, + &subghz_protocol_keeloq, + &subghz_protocol_star_line, + &subghz_protocol_nice_flo, + &subghz_protocol_came, + &subghz_protocol_faac_slh, + &subghz_protocol_nice_flor_s, + &subghz_protocol_came_twee, + &subghz_protocol_came_atomo, + &subghz_protocol_nero_sketch, + &subghz_protocol_ido, + &subghz_protocol_kia, + &subghz_protocol_hormann, + &subghz_protocol_nero_radio, + &subghz_protocol_somfy_telis, + &subghz_protocol_somfy_keytis, + &subghz_protocol_scher_khan, + &subghz_protocol_princeton, + &subghz_protocol_raw, + &subghz_protocol_linear, + &subghz_protocol_secplus_v2, + &subghz_protocol_secplus_v1, + &subghz_protocol_megacode, + &subghz_protocol_holtek, + &subghz_protocol_chamb_code, + &subghz_protocol_power_smart, + &subghz_protocol_marantec, + &subghz_protocol_bett, + &subghz_protocol_doitrand, + &subghz_protocol_phoenix_v2, + &subghz_protocol_honeywell, // Should be before honeywell_wdb + &subghz_protocol_honeywell_wdb, + &subghz_protocol_magellan, + &subghz_protocol_intertechno_v3, + &subghz_protocol_clemsa, + &subghz_protocol_ansonic, + &subghz_protocol_smc5326, + &subghz_protocol_holtek_th12x, + &subghz_protocol_linear_delta3, + &subghz_protocol_dooya, + &subghz_protocol_alutech_at_4n, + &subghz_protocol_kinggates_stylo_4k, + &ws_protocol_infactory, + &ws_protocol_thermopro_tx4, + &ws_protocol_nexus_th, + &ws_protocol_gt_wt_02, + &ws_protocol_gt_wt_03, + &ws_protocol_acurite_606tx, + &ws_protocol_acurite_609txc, + &ws_protocol_acurite_986, + &ws_protocol_lacrosse_tx, + &ws_protocol_lacrosse_tx141thbv2, + &ws_protocol_oregon2, + &ws_protocol_oregon3, + &ws_protocol_acurite_592txr, + &ws_protocol_ambient_weather, + &ws_protocol_auriol_th, + &ws_protocol_oregon_v1, + &ws_protocol_tx_8300, + &ws_protocol_wendox_w6726, + &ws_protocol_auriol_ahfl, + &ws_protocol_kedsum_th, + &subghz_protocol_pocsag, + &tpms_protocol_schrader_gg4, + &subghz_protocol_bin_raw, + &subghz_protocol_genie, + &subghz_protocol_mastercode, + &subghz_protocol_x10, }; const SubGhzProtocolRegistry subghz_protocol_registry = { diff --git a/lib/subghz/protocols/protocol_items.h b/lib/subghz/protocols/protocol_items.h index cc2721cddcc..fef842ef65a 100644 --- a/lib/subghz/protocols/protocol_items.h +++ b/lib/subghz/protocols/protocol_items.h @@ -44,7 +44,29 @@ #include "dooya.h" #include "alutech_at_4n.h" #include "kinggates_stylo_4k.h" +#include "infactory.h" +#include "thermopro_tx4.h" +#include "nexus_th.h" +#include "gt_wt_02.h" +#include "gt_wt_03.h" +#include "acurite_606tx.h" +#include "acurite_609txc.h" +#include "acurite_986.h" +#include "lacrosse_tx.h" +#include "lacrosse_tx141thbv2.h" +#include "oregon2.h" +#include "oregon3.h" +#include "acurite_592txr.h" +#include "ambient_weather.h" +#include "auriol_hg0601a.h" +#include "oregon_v1.h" +#include "tx_8300.h" +#include "wendox_w6726.h" +#include "auriol_ahfl.h" +#include "kedsum_th.h" +#include "pocsag.h" +#include "schrader_gg4.h" #include "bin_raw.h" -#include "x10.h" #include "genie.h" #include "mastercode.h" +#include "x10.h" diff --git a/lib/subghz/protocols/scher_khan.c b/lib/subghz/protocols/scher_khan.c index 53b7935d692..dfe6c4a8fee 100644 --- a/lib/subghz/protocols/scher_khan.c +++ b/lib/subghz/protocols/scher_khan.c @@ -70,10 +70,12 @@ const SubGhzProtocol subghz_protocol_scher_khan = { .name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Save | SubGhzProtocolFlag_AutoAlarms, + SubGhzProtocolFlag_Save, .decoder = &subghz_protocol_scher_khan_decoder, .encoder = &subghz_protocol_scher_khan_encoder, + + .filter = SubGhzProtocolFilter_AutoAlarms, }; void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) { @@ -272,10 +274,10 @@ static void subghz_protocol_scher_khan_check_remote_controller( } } -uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderScherKhan* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/scher_khan.h b/lib/subghz/protocols/scher_khan.h index 58545069cf6..759afa875a2 100644 --- a/lib/subghz/protocols/scher_khan.h +++ b/lib/subghz/protocols/scher_khan.h @@ -43,7 +43,7 @@ void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderScherKhan instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderScherKhan. diff --git a/lib/subghz/protocols/schrader_gg4.c b/lib/subghz/protocols/schrader_gg4.c new file mode 100644 index 00000000000..5624049758a --- /dev/null +++ b/lib/subghz/protocols/schrader_gg4.c @@ -0,0 +1,297 @@ +#include "schrader_gg4.h" +#include + +#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 = 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); +} diff --git a/lib/subghz/protocols/schrader_gg4.h b/lib/subghz/protocols/schrader_gg4.h new file mode 100644 index 00000000000..86a71a977c2 --- /dev/null +++ b/lib/subghz/protocols/schrader_gg4.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "tpms_generic.h" +#include + +#define TPMS_PROTOCOL_SCHRADER_GG4_NAME "Schrader GG4" + +typedef struct TPMSProtocolDecoderSchraderGG4 TPMSProtocolDecoderSchraderGG4; +typedef struct TPMSProtocolEncoderSchraderGG4 TPMSProtocolEncoderSchraderGG4; + +extern const SubGhzProtocolDecoder tpms_protocol_schrader_gg4_decoder; +extern const SubGhzProtocolEncoder tpms_protocol_schrader_gg4_encoder; +extern const SubGhzProtocol tpms_protocol_schrader_gg4; + +/** + * Allocate TPMSProtocolDecoderSchraderGG4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return TPMSProtocolDecoderSchraderGG4* pointer to a TPMSProtocolDecoderSchraderGG4 instance + */ +void* tpms_protocol_decoder_schrader_gg4_alloc(SubGhzEnvironment* environment); + +/** + * Free TPMSProtocolDecoderSchraderGG4. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + */ +void tpms_protocol_decoder_schrader_gg4_free(void* context); + +/** + * Reset decoder TPMSProtocolDecoderSchraderGG4. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + */ +void tpms_protocol_decoder_schrader_gg4_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void tpms_protocol_decoder_schrader_gg4_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + * @return hash Hash sum + */ +uint32_t tpms_protocol_decoder_schrader_gg4_get_hash_data(void* context); + +/** + * Serialize data TPMSProtocolDecoderSchraderGG4. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus tpms_protocol_decoder_schrader_gg4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data TPMSProtocolDecoderSchraderGG4. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + tpms_protocol_decoder_schrader_gg4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a TPMSProtocolDecoderSchraderGG4 instance + * @param output Resulting text + */ +void tpms_protocol_decoder_schrader_gg4_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/secplus_v1.c b/lib/subghz/protocols/secplus_v1.c index 8d41582bf46..d754d4328bd 100644 --- a/lib/subghz/protocols/secplus_v1.c +++ b/lib/subghz/protocols/secplus_v1.c @@ -510,10 +510,10 @@ void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v1* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/secplus_v1.h b/lib/subghz/protocols/secplus_v1.h index e01f8bcdaa4..aef17f390c7 100644 --- a/lib/subghz/protocols/secplus_v1.h +++ b/lib/subghz/protocols/secplus_v1.h @@ -79,7 +79,7 @@ void subghz_protocol_decoder_secplus_v1_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v1 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_secplus_v1_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderSecPlus_v1. diff --git a/lib/subghz/protocols/secplus_v2.c b/lib/subghz/protocols/secplus_v2.c index 3df74ba1db6..0aa5e2508de 100644 --- a/lib/subghz/protocols/secplus_v2.c +++ b/lib/subghz/protocols/secplus_v2.c @@ -771,10 +771,10 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t } } -uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSecPlus_v2* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/secplus_v2.h b/lib/subghz/protocols/secplus_v2.h index 9eb912a27e1..37a6bb5ffa3 100644 --- a/lib/subghz/protocols/secplus_v2.h +++ b/lib/subghz/protocols/secplus_v2.h @@ -79,7 +79,7 @@ void subghz_protocol_decoder_secplus_v2_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderSecPlus_v2 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_secplus_v2_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderSecPlus_v2. diff --git a/lib/subghz/protocols/smc5326.c b/lib/subghz/protocols/smc5326.c index 0b9755b8cba..dd990b74bb1 100644 --- a/lib/subghz/protocols/smc5326.c +++ b/lib/subghz/protocols/smc5326.c @@ -308,10 +308,10 @@ void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t du } } -uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_smc5326_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSMC5326* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/smc5326.h b/lib/subghz/protocols/smc5326.h index 911226cf9bd..ab3be0bf8da 100644 --- a/lib/subghz/protocols/smc5326.h +++ b/lib/subghz/protocols/smc5326.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_smc5326_feed(void* context, bool level, uint32_t du * @param context Pointer to a SubGhzProtocolDecoderSMC5326 instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_smc5326_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderSMC5326. diff --git a/lib/subghz/protocols/somfy_keytis.c b/lib/subghz/protocols/somfy_keytis.c index 22d2b5e9fa4..110b3d2d73c 100644 --- a/lib/subghz/protocols/somfy_keytis.c +++ b/lib/subghz/protocols/somfy_keytis.c @@ -723,10 +723,10 @@ static const char* subghz_protocol_somfy_keytis_get_name_button(uint8_t btn) { return btn <= 0xf ? name_btn[btn] : name_btn[0]; } -uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSomfyKeytis* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/somfy_keytis.h b/lib/subghz/protocols/somfy_keytis.h index d7e8df109b9..0a774e2ff3e 100644 --- a/lib/subghz/protocols/somfy_keytis.h +++ b/lib/subghz/protocols/somfy_keytis.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_somfy_keytis_feed(void* context, bool level, uint32 * @param context Pointer to a SubGhzProtocolDecoderSomfyKeytis instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_somfy_keytis_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderSomfyKeytis. diff --git a/lib/subghz/protocols/somfy_telis.c b/lib/subghz/protocols/somfy_telis.c index b198ce49132..4f6c43a7a29 100644 --- a/lib/subghz/protocols/somfy_telis.c +++ b/lib/subghz/protocols/somfy_telis.c @@ -642,10 +642,10 @@ static const char* subghz_protocol_somfy_telis_get_name_button(uint8_t btn) { return btn <= 0xf ? name_btn[btn] : name_btn[0]; } -uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderSomfyTelis* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/somfy_telis.h b/lib/subghz/protocols/somfy_telis.h index a072079dab8..81a8ee0cb6a 100644 --- a/lib/subghz/protocols/somfy_telis.h +++ b/lib/subghz/protocols/somfy_telis.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_somfy_telis_feed(void* context, bool level, uint32_ * @param context Pointer to a SubGhzProtocolDecoderSomfyTelis instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_somfy_telis_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderSomfyTelis. diff --git a/lib/subghz/protocols/star_line.c b/lib/subghz/protocols/star_line.c index 75a7fd471fc..87483e748d2 100644 --- a/lib/subghz/protocols/star_line.c +++ b/lib/subghz/protocols/star_line.c @@ -79,11 +79,12 @@ const SubGhzProtocol subghz_protocol_star_line = { .name = SUBGHZ_PROTOCOL_STAR_LINE_NAME, .type = SubGhzProtocolTypeDynamic, .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | - SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send | - SubGhzProtocolFlag_StarLine, + SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send, .decoder = &subghz_protocol_star_line_decoder, .encoder = &subghz_protocol_star_line_encoder, + + .filter = SubGhzProtocolFilter_StarLine, }; /** @@ -637,10 +638,10 @@ static void subghz_protocol_star_line_check_remote_controller( instance->btn = key_fix >> 24; } -uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_star_line_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderStarLine* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/star_line.h b/lib/subghz/protocols/star_line.h index e7b15ca0b72..08d6ba96e21 100644 --- a/lib/subghz/protocols/star_line.h +++ b/lib/subghz/protocols/star_line.h @@ -78,7 +78,7 @@ void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t * @param context Pointer to a SubGhzProtocolDecoderStarLine instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_star_line_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderStarLine. diff --git a/lib/subghz/protocols/thermopro_tx4.c b/lib/subghz/protocols/thermopro_tx4.c new file mode 100644 index 00000000000..5f051bd0e43 --- /dev/null +++ b/lib/subghz/protocols/thermopro_tx4.c @@ -0,0 +1,254 @@ +#include "thermopro_tx4.h" + +#define TAG "WSProtocolThermoPRO_TX4" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/thermopro_tx2.c + * + * The sensor sends 37 bits 6 times, before the first packet there is a sync pulse. + * The packets are ppm modulated (distance coding) with a pulse of ~500 us + * followed by a short gap of ~2000 us for a 0 bit or a long ~4000 us gap for a + * 1 bit, the sync gap is ~9000 us. + * The data is grouped in 9 nibbles + * [type] [id0] [id1] [flags] [temp0] [temp1] [temp2] [humi0] [humi1] + * - type: 4 bit fixed 1001 (9) or 0110 (5) + * - id: 8 bit a random id that is generated when the sensor starts, could include battery status + * the same batteries often generate the same id + * - flags(3): is 1 when the battery is low, otherwise 0 (ok) + * - flags(2): is 1 when the sensor sends a reading when pressing the button on the sensor + * - flags(1,0): the channel number that can be set by the sensor (1, 2, 3, X) + * - temp: 12 bit signed scaled by 10 + * - humi: 8 bit always 11001100 (0xCC) if no humidity sensor is available + * + */ + +#define THERMO_PRO_TX4_TYPE_1 0b1001 +#define THERMO_PRO_TX4_TYPE_2 0b0110 + +static const SubGhzBlockConst ws_protocol_thermopro_tx4_const = { + .te_short = 500, + .te_long = 2000, + .te_delta = 150, + .min_count_bit_for_found = 37, +}; + +struct WSProtocolDecoderThermoPRO_TX4 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; +}; + +struct WSProtocolEncoderThermoPRO_TX4 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + ThermoPRO_TX4DecoderStepReset = 0, + ThermoPRO_TX4DecoderStepSaveDuration, + ThermoPRO_TX4DecoderStepCheckDuration, +} ThermoPRO_TX4DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder = { + .alloc = ws_protocol_decoder_thermopro_tx4_alloc, + .free = ws_protocol_decoder_thermopro_tx4_free, + + .feed = ws_protocol_decoder_thermopro_tx4_feed, + .reset = ws_protocol_decoder_thermopro_tx4_reset, + + .get_hash_data = ws_protocol_decoder_thermopro_tx4_get_hash_data, + .serialize = ws_protocol_decoder_thermopro_tx4_serialize, + .deserialize = ws_protocol_decoder_thermopro_tx4_deserialize, + .get_string = ws_protocol_decoder_thermopro_tx4_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_thermopro_tx4 = { + .name = WS_PROTOCOL_THERMOPRO_TX4_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_thermopro_tx4_decoder, + .encoder = &ws_protocol_thermopro_tx4_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderThermoPRO_TX4* instance = malloc(sizeof(WSProtocolDecoderThermoPRO_TX4)); + instance->base.protocol = &ws_protocol_thermopro_tx4; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_thermopro_tx4_free(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + free(instance); +} + +void ws_protocol_decoder_thermopro_tx4_reset(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; +} + +static bool ws_protocol_thermopro_tx4_check(WSProtocolDecoderThermoPRO_TX4* instance) { + uint8_t type = instance->decoder.decode_data >> 33; + + if((type == THERMO_PRO_TX4_TYPE_1) || (type == THERMO_PRO_TX4_TYPE_2)) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_thermopro_tx4_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 25) & 0xFF; + instance->battery_low = (instance->data >> 24) & 1; + instance->btn = (instance->data >> 23) & 1; + instance->channel = ((instance->data >> 21) & 0x03) + 1; + + if(!((instance->data >> 20) & 1)) { + instance->temp = (float)((instance->data >> 9) & 0x07FF) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 9) & 0x07FF) + 1) / -10.0f; + } + + instance->humidity = (instance->data >> 1) & 0xFF; +} + +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + + switch(instance->decoder.parser_step) { + case ThermoPRO_TX4DecoderStepReset: + if((!level) && (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10)) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + } + break; + + case ThermoPRO_TX4DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + + case ThermoPRO_TX4DecoderStepCheckDuration: + if(!level) { + if(DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_short * 18) < + ws_protocol_thermopro_tx4_const.te_delta * 10) { + //Found sync + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + if((instance->decoder.decode_count_bit == + ws_protocol_thermopro_tx4_const.min_count_bit_for_found) && + ws_protocol_thermopro_tx4_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_thermopro_tx4_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepCheckDuration; + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + + break; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long) < + ws_protocol_thermopro_tx4_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_thermopro_tx4_const.te_short) < + ws_protocol_thermopro_tx4_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_thermopro_tx4_const.te_long * 2) < + ws_protocol_thermopro_tx4_const.te_delta * 4)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + } else { + instance->decoder.parser_step = ThermoPRO_TX4DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_thermopro_tx4_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderThermoPRO_TX4* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/thermopro_tx4.h b/lib/subghz/protocols/thermopro_tx4.h new file mode 100644 index 00000000000..f4262db1ca1 --- /dev/null +++ b/lib/subghz/protocols/thermopro_tx4.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_THERMOPRO_TX4_NAME "ThermoPRO-TX4" + +typedef struct WSProtocolDecoderThermoPRO_TX4 WSProtocolDecoderThermoPRO_TX4; +typedef struct WSProtocolEncoderThermoPRO_TX4 WSProtocolEncoderThermoPRO_TX4; + +extern const SubGhzProtocolDecoder ws_protocol_thermopro_tx4_decoder; +extern const SubGhzProtocolEncoder ws_protocol_thermopro_tx4_encoder; +extern const SubGhzProtocol ws_protocol_thermopro_tx4; + +/** + * Allocate WSProtocolDecoderThermoPRO_TX4. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderThermoPRO_TX4* pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void* ws_protocol_decoder_thermopro_tx4_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_free(void* context); + +/** + * Reset decoder WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + */ +void ws_protocol_decoder_thermopro_tx4_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_thermopro_tx4_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_thermopro_tx4_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_thermopro_tx4_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderThermoPRO_TX4. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_thermopro_tx4_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderThermoPRO_TX4 instance + * @param output Resulting text + */ +void ws_protocol_decoder_thermopro_tx4_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/tpms_generic.c b/lib/subghz/protocols/tpms_generic.c new file mode 100644 index 00000000000..0251c21615b --- /dev/null +++ b/lib/subghz/protocols/tpms_generic.c @@ -0,0 +1,237 @@ +#include "tpms_generic.h" +#include +#include + +#define TAG "TPMSBlockGeneric" + +void tpms_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +SubGhzProtocolStatus tpms_block_generic_serialize( + TPMSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, TPMS_KEY_FILE_TYPE, TPMS_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + res = SubGhzProtocolStatusErrorParserHeader; + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + res = SubGhzProtocolStatusErrorParserFrequency; + break; + } + + tpms_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + res = SubGhzProtocolStatusErrorParserPreset; + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + res = SubGhzProtocolStatusErrorParserCustomPreset; + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + res = SubGhzProtocolStatusErrorParserCustomPreset; + break; + } + } + if(!flipper_format_write_float(flipper_format, "Latitute", &preset->latitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Latitute"); + res = SubGhzProtocolStatusErrorParserLatitude; + break; + } + if(!flipper_format_write_float(flipper_format, "Longitude", &preset->longitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Longitude"); + res = SubGhzProtocolStatusErrorParserLongitude; + break; + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + res = SubGhzProtocolStatusErrorParserProtocolName; + break; + } + + uint32_t temp_data = instance->id; + if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Id"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; + 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->data >> (i * 8)) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->battery_low; + if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + float temp = instance->pressure; + if(!flipper_format_write_float(flipper_format, "Pressure", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Pressure"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + //DATE AGE set + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + temp_data = curr_ts; + if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp = instance->temperature; + if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + res = SubGhzProtocolStatusOk; + } while(false); + furi_string_free(temp_str); + return res; +} + +SubGhzProtocolStatus + tpms_block_generic_deserialize(TPMSBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Id", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->id = temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Bit", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; + break; + } + instance->data_count_bit = (uint8_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + if(!flipper_format_read_uint32(flipper_format, "Batt", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->battery_low = (uint8_t)temp_data; + + float temp; + if(!flipper_format_read_float(flipper_format, "Pressure", &temp, 1)) { + FURI_LOG_E(TAG, "Missing Pressure"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->pressure = temp; + + if(!flipper_format_read_uint32(flipper_format, "Ts", &temp_data, 1)) { + FURI_LOG_E(TAG, "Missing timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->timestamp = temp_data; + + if(!flipper_format_read_float(flipper_format, "Temp", &temp, 1)) { + FURI_LOG_E(TAG, "Missing Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->temperature = temp; + + res = SubGhzProtocolStatusOk; + } while(0); + + return res; +} + +SubGhzProtocolStatus tpms_block_generic_deserialize_check_count_bit( + TPMSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit) { + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = tpms_block_generic_deserialize(instance, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->data_count_bit != count_bit) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} \ No newline at end of file diff --git a/lib/subghz/protocols/tpms_generic.h b/lib/subghz/protocols/tpms_generic.h new file mode 100644 index 00000000000..d57815678b5 --- /dev/null +++ b/lib/subghz/protocols/tpms_generic.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define TPMS_KEY_FILE_VERSION 1 +#define TPMS_KEY_FILE_TYPE "Flipper SubGhz Key File" + +#define TPMS_NO_BATT 0xFF + +typedef struct TPMSBlockGeneric TPMSBlockGeneric; + +struct TPMSBlockGeneric { + const char* protocol_name; + uint64_t data; + uint8_t data_count_bit; + + uint32_t timestamp; + + uint32_t id; + uint8_t battery_low; + // bool storage; + float pressure; // bar + float temperature; // celsius +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void tpms_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data TPMSBlockGeneric. + * @param instance Pointer to a TPMSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus tpms_block_generic_serialize( + TPMSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data TPMSBlockGeneric. + * @param instance Pointer to a TPMSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + tpms_block_generic_deserialize(TPMSBlockGeneric* instance, FlipperFormat* flipper_format); + +/** + * Deserialize data TPMSBlockGeneric. + * @param instance Pointer to a TPMSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param count_bit Count bit protocol + * @return status + */ +SubGhzProtocolStatus tpms_block_generic_deserialize_check_count_bit( + TPMSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/protocols/tx_8300.c b/lib/subghz/protocols/tx_8300.c new file mode 100644 index 00000000000..18fef24b084 --- /dev/null +++ b/lib/subghz/protocols/tx_8300.c @@ -0,0 +1,287 @@ +#include "tx_8300.h" + +#define TAG "WSProtocolTX_8300" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/ambientweather_tx8300.c + * + * Ambient Weather TX-8300 (also sold as TFA 30.3211.02). + * 1970us pulse with variable gap (third pulse 3920 us). + * Above 79% humidity, gap after third pulse is 5848 us. + * - Bit 1 : 1970us pulse with 3888 us gap + * - Bit 0 : 1970us pulse with 1936 us gap + * 74 bit (2 bit preamble and 72 bit data => 9 bytes => 18 nibbles) + * The preamble seems to be a repeat counter (00, and 01 seen), + * the first 4 bytes are data, + * the second 4 bytes the same data inverted, + * the last byte is a checksum. + * Preamble format (2 bits): + * [1 bit (0)] [1 bit rolling count] + * Payload format (32 bits): + * HHHHhhhh ??CCNIII IIIITTTT ttttuuuu + * - H = First BCD digit humidity (the MSB might be distorted by the demod) + * - h = Second BCD digit humidity, invalid humidity seems to be 0x0e + * - ? = Likely battery flag, 2 bits + * - C = Channel, 2 bits + * - N = Negative temperature sign bit + * - I = ID, 7-bit + * - T = First BCD digit temperature + * - t = Second BCD digit temperature + * - u = Third BCD digit temperature + * The Checksum seems to covers the 4 data bytes and is something like Fletcher-8. + **/ + +#define TX_8300_PACKAGE_SIZE 32 + +static const SubGhzBlockConst ws_protocol_tx_8300_const = { + .te_short = 1940, + .te_long = 3880, + .te_delta = 250, + .min_count_bit_for_found = 72, +}; + +struct WSProtocolDecoderTX_8300 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + uint32_t package_1; + uint32_t package_2; +}; + +struct WSProtocolEncoderTX_8300 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + TX_8300DecoderStepReset = 0, + TX_8300DecoderStepCheckPreambule, + TX_8300DecoderStepSaveDuration, + TX_8300DecoderStepCheckDuration, +} TX_8300DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder = { + .alloc = ws_protocol_decoder_tx_8300_alloc, + .free = ws_protocol_decoder_tx_8300_free, + + .feed = ws_protocol_decoder_tx_8300_feed, + .reset = ws_protocol_decoder_tx_8300_reset, + + .get_hash_data = ws_protocol_decoder_tx_8300_get_hash_data, + .serialize = ws_protocol_decoder_tx_8300_serialize, + .deserialize = ws_protocol_decoder_tx_8300_deserialize, + .get_string = ws_protocol_decoder_tx_8300_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_tx_8300 = { + .name = WS_PROTOCOL_TX_8300_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_tx_8300_decoder, + .encoder = &ws_protocol_tx_8300_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderTX_8300* instance = malloc(sizeof(WSProtocolDecoderTX_8300)); + instance->base.protocol = &ws_protocol_tx_8300; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_tx_8300_free(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + free(instance); +} + +void ws_protocol_decoder_tx_8300_reset(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + instance->decoder.parser_step = TX_8300DecoderStepReset; +} + +static bool ws_protocol_tx_8300_check_crc(WSProtocolDecoderTX_8300* instance) { + if(!instance->package_2) return false; + if(instance->package_1 != ~instance->package_2) return false; + + uint16_t x = 0; + uint16_t y = 0; + for(int i = 0; i < 32; i += 4) { + x += (instance->package_1 >> i) & 0x0F; + y += (instance->package_1 >> i) & 0x05; + } + uint8_t crc = (~x & 0xF) << 4 | (~y & 0xF); + return (crc == ((instance->decoder.decode_data) & 0xFF)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_tx_8300_remote_controller(WSBlockGeneric* instance) { + instance->humidity = (((instance->data >> 28) & 0x0F) * 10) + ((instance->data >> 24) & 0x0F); + instance->btn = WS_NO_BTN; + if(!((instance->data >> 22) & 0x03)) + instance->battery_low = 0; + else + instance->battery_low = 1; + instance->channel = (instance->data >> 20) & 0x03; + instance->id = (instance->data >> 12) & 0x7F; + + float temp_raw = ((instance->data >> 8) & 0x0F) * 10.0f + ((instance->data >> 4) & 0x0F) + + (instance->data & 0x0F) * 0.1f; + if(!((instance->data >> 19) & 1)) { + instance->temp = temp_raw; + } else { + instance->temp = -temp_raw; + } +} + +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + + switch(instance->decoder.parser_step) { + case TX_8300DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta)) { + instance->decoder.parser_step = TX_8300DecoderStepCheckPreambule; + } + break; + + case TX_8300DecoderStepCheckPreambule: + if((!level) && ((DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 2) < + ws_protocol_tx_8300_const.te_delta) || + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short * 3) < + ws_protocol_tx_8300_const.te_delta))) { + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->package_1 = 0; + instance->package_2 = 0; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = TX_8300DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + + case TX_8300DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_tx_8300_const.te_short * 5)) { + //Found syncPostfix + if((instance->decoder.decode_count_bit == + ws_protocol_tx_8300_const.min_count_bit_for_found) && + ws_protocol_tx_8300_check_crc(instance)) { + instance->generic.data = instance->package_1; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_tx_8300_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 1; + instance->decoder.parser_step = TX_8300DecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_long) < + ws_protocol_tx_8300_const.te_delta * 2)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_tx_8300_const.te_short) < + ws_protocol_tx_8300_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = TX_8300DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + + if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE) { + instance->package_1 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } else if(instance->decoder.decode_count_bit == TX_8300_PACKAGE_SIZE * 2) { + instance->package_2 = instance->decoder.decode_data; + instance->decoder.decode_data = 0; + } + + } else { + instance->decoder.parser_step = TX_8300DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_tx_8300_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_tx_8300_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderTX_8300* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/tx_8300.h b/lib/subghz/protocols/tx_8300.h new file mode 100644 index 00000000000..536ca28b556 --- /dev/null +++ b/lib/subghz/protocols/tx_8300.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_TX_8300_NAME "TX8300" + +typedef struct WSProtocolDecoderTX_8300 WSProtocolDecoderTX_8300; +typedef struct WSProtocolEncoderTX_8300 WSProtocolEncoderTX_8300; + +extern const SubGhzProtocolDecoder ws_protocol_tx_8300_decoder; +extern const SubGhzProtocolEncoder ws_protocol_tx_8300_encoder; +extern const SubGhzProtocol ws_protocol_tx_8300; + +/** + * Allocate WSProtocolDecoderTX_8300. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderTX_8300* pointer to a WSProtocolDecoderTX_8300 instance + */ +void* ws_protocol_decoder_tx_8300_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_free(void* context); + +/** + * Reset decoder WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + */ +void ws_protocol_decoder_tx_8300_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_tx_8300_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_tx_8300_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_tx_8300_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderTX_8300. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_tx_8300_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderTX_8300 instance + * @param output Resulting text + */ +void ws_protocol_decoder_tx_8300_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/wendox_w6726.c b/lib/subghz/protocols/wendox_w6726.c new file mode 100644 index 00000000000..a9a05f0164f --- /dev/null +++ b/lib/subghz/protocols/wendox_w6726.c @@ -0,0 +1,302 @@ +#include "wendox_w6726.h" + +#define TAG "WSProtocolWendoxW6726" + +/* + * Wendox W6726 + * + * Temperature -50C to +70C + * _ _ _ __ _ + * _| |___| |___| |___ ... | |_| |__...._______________ + * preamble data guard time + * + * 3 reps every 3 minutes + * in the first message 11 bytes of the preamble in the rest by 7 + * + * bit 0: 1955-hi, 5865-lo + * bit 1: 5865-hi, 1955-lo + * guard time: 12*1955+(lo last bit) + * data: 29 bit + * + * IIIII | ZTTTTTTTTT | uuuuuuuBuu | CCCC + * + * I: identification; + * Z: temperature sign; + * T: temperature sign dependent +12C; + * B: battery low; flag to indicate low battery voltage; + * C: CRC4 (polynomial = 0x9, start_data = 0xD); + * u: unknown; + */ + +static const SubGhzBlockConst ws_protocol_wendox_w6726_const = { + .te_short = 1955, + .te_long = 5865, + .te_delta = 300, + .min_count_bit_for_found = 29, +}; + +struct WSProtocolDecoderWendoxW6726 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderWendoxW6726 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + WendoxW6726DecoderStepReset = 0, + WendoxW6726DecoderStepCheckPreambule, + WendoxW6726DecoderStepSaveDuration, + WendoxW6726DecoderStepCheckDuration, +} WendoxW6726DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder = { + .alloc = ws_protocol_decoder_wendox_w6726_alloc, + .free = ws_protocol_decoder_wendox_w6726_free, + + .feed = ws_protocol_decoder_wendox_w6726_feed, + .reset = ws_protocol_decoder_wendox_w6726_reset, + + .get_hash_data = ws_protocol_decoder_wendox_w6726_get_hash_data, + .serialize = ws_protocol_decoder_wendox_w6726_serialize, + .deserialize = ws_protocol_decoder_wendox_w6726_deserialize, + .get_string = ws_protocol_decoder_wendox_w6726_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_wendox_w6726 = { + .name = WS_PROTOCOL_WENDOX_W6726_NAME, + .type = SubGhzProtocolTypeStatic, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | + SubGhzProtocolFlag_Save, + + .decoder = &ws_protocol_wendox_w6726_decoder, + .encoder = &ws_protocol_wendox_w6726_encoder, + + .filter = SubGhzProtocolFilter_Weather, +}; + +void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderWendoxW6726* instance = malloc(sizeof(WSProtocolDecoderWendoxW6726)); + instance->base.protocol = &ws_protocol_wendox_w6726; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_wendox_w6726_free(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + free(instance); +} + +void ws_protocol_decoder_wendox_w6726_reset(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + instance->decoder.parser_step = WendoxW6726DecoderStepReset; +} + +static bool ws_protocol_wendox_w6726_check(WSProtocolDecoderWendoxW6726* instance) { + if(!instance->decoder.decode_data) return false; + uint8_t msg[] = { + instance->decoder.decode_data >> 28, + instance->decoder.decode_data >> 20, + instance->decoder.decode_data >> 12, + instance->decoder.decode_data >> 4}; + + uint8_t crc = subghz_protocol_blocks_crc4(msg, 4, 0x9, 0xD); + return (crc == (instance->decoder.decode_data & 0x0F)); +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_wendox_w6726_remote_controller(WSBlockGeneric* instance) { + instance->id = (instance->data >> 24) & 0xFF; + instance->battery_low = (instance->data >> 6) & 1; + instance->channel = WS_NO_CHANNEL; + + if(((instance->data >> 23) & 1)) { + instance->temp = (float)(((instance->data >> 14) & 0x1FF) + 12) / 10.0f; + } else { + instance->temp = (float)((~(instance->data >> 14) & 0x1FF) + 1 - 12) / -10.0f; + } + + if(instance->temp < -50.0f) { + instance->temp = -50.0f; + } else if(instance->temp > 70.0f) { + instance->temp = 70.0f; + } + + instance->btn = WS_NO_BTN; + instance->humidity = WS_NO_HUMIDITY; +} + +void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + + switch(instance->decoder.parser_step) { + case WendoxW6726DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + instance->decoder.parser_step = WendoxW6726DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case WendoxW6726DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta * 1) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2)) { + instance->header_count++; + } else if((instance->header_count > 4) && (instance->header_count < 12)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + } + break; + + case WendoxW6726DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = WendoxW6726DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + break; + + case WendoxW6726DecoderStepCheckDuration: + if(!level) { + if(duration > + ws_protocol_wendox_w6726_const.te_short + ws_protocol_wendox_w6726_const.te_long) { + if(DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else if( + DURATION_DIFF( + instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + if((instance->decoder.decode_count_bit == + ws_protocol_wendox_w6726_const.min_count_bit_for_found) && + ws_protocol_wendox_w6726_check(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_wendox_w6726_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 3)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_wendox_w6726_const.te_long) < + ws_protocol_wendox_w6726_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_wendox_w6726_const.te_short) < + ws_protocol_wendox_w6726_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = WendoxW6726DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + } else { + instance->decoder.parser_step = WendoxW6726DecoderStepReset; + } + break; + } +} + +uint32_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return subghz_protocol_blocks_get_hash_data_long( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, + flipper_format, + ws_protocol_wendox_w6726_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderWendoxW6726* instance = context; + furi_string_cat_printf( + output, + "%s\r\n%dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/lib/subghz/protocols/wendox_w6726.h b/lib/subghz/protocols/wendox_w6726.h new file mode 100644 index 00000000000..3043c6b7138 --- /dev/null +++ b/lib/subghz/protocols/wendox_w6726.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_WENDOX_W6726_NAME "Wendox W6726" + +typedef struct WSProtocolDecoderWendoxW6726 WSProtocolDecoderWendoxW6726; +typedef struct WSProtocolEncoderWendoxW6726 WSProtocolEncoderWendoxW6726; + +extern const SubGhzProtocolDecoder ws_protocol_wendox_w6726_decoder; +extern const SubGhzProtocolEncoder ws_protocol_wendox_w6726_encoder; +extern const SubGhzProtocol ws_protocol_wendox_w6726; + +/** + * Allocate WSProtocolDecoderWendoxW6726. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderWendoxW6726* pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void* ws_protocol_decoder_wendox_w6726_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void ws_protocol_decoder_wendox_w6726_free(void* context); + +/** + * Reset decoder WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + */ +void ws_protocol_decoder_wendox_w6726_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_wendox_w6726_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @return hash Hash sum + */ +uint32_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_wendox_w6726_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderWendoxW6726. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_wendox_w6726_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderWendoxW6726 instance + * @param output Resulting text + */ +void ws_protocol_decoder_wendox_w6726_get_string(void* context, FuriString* output); diff --git a/lib/subghz/protocols/ws_generic.c b/lib/subghz/protocols/ws_generic.c new file mode 100644 index 00000000000..18d35f48a5c --- /dev/null +++ b/lib/subghz/protocols/ws_generic.c @@ -0,0 +1,268 @@ +#include "ws_generic.h" +#include +#include +//#include "../helpers/weather_station_types.h" + +#define TAG "WSBlockGeneric" + +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str) { + const char* preset_name_temp; + if(!strcmp(preset_name, "AM270")) { + preset_name_temp = "FuriHalSubGhzPresetOok270Async"; + } else if(!strcmp(preset_name, "AM_Q")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async_q"; + } else if(!strcmp(preset_name, "AM650")) { + preset_name_temp = "FuriHalSubGhzPresetOok650Async"; + } else if(!strcmp(preset_name, "FM238")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev238Async"; + } else if(!strcmp(preset_name, "FM476")) { + preset_name_temp = "FuriHalSubGhzPreset2FSKDev476Async"; + } else { + preset_name_temp = "FuriHalSubGhzPresetCustom"; + } + furi_string_set(preset_str, preset_name_temp); +} + +SubGhzProtocolStatus ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + FuriString* temp_str; + temp_str = furi_string_alloc(); + do { + stream_clean(flipper_format_get_raw_stream(flipper_format)); + if(!flipper_format_write_header_cstr( + flipper_format, WS_KEY_FILE_TYPE, WS_KEY_FILE_VERSION)) { + FURI_LOG_E(TAG, "Unable to add header"); + res = SubGhzProtocolStatusErrorParserHeader; + break; + } + + if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) { + FURI_LOG_E(TAG, "Unable to add Frequency"); + res = SubGhzProtocolStatusErrorParserFrequency; + break; + } + + ws_block_generic_get_preset_name(furi_string_get_cstr(preset->name), temp_str); + if(!flipper_format_write_string_cstr( + flipper_format, "Preset", furi_string_get_cstr(temp_str))) { + FURI_LOG_E(TAG, "Unable to add Preset"); + res = SubGhzProtocolStatusErrorParserPreset; + break; + } + if(!strcmp(furi_string_get_cstr(temp_str), "FuriHalSubGhzPresetCustom")) { + if(!flipper_format_write_string_cstr( + flipper_format, "Custom_preset_module", "CC1101")) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_module"); + res = SubGhzProtocolStatusErrorParserCustomPreset; + break; + } + if(!flipper_format_write_hex( + flipper_format, "Custom_preset_data", preset->data, preset->data_size)) { + FURI_LOG_E(TAG, "Unable to add Custom_preset_data"); + res = SubGhzProtocolStatusErrorParserCustomPreset; + break; + } + } + if(!flipper_format_write_float(flipper_format, "Latitute", &preset->latitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Latitute"); + res = SubGhzProtocolStatusErrorParserLatitude; + break; + } + if(!flipper_format_write_float(flipper_format, "Longitude", &preset->longitude, 1)) { + FURI_LOG_E(TAG, "Unable to add Longitude"); + res = SubGhzProtocolStatusErrorParserLongitude; + break; + } + if(!flipper_format_write_string_cstr(flipper_format, "Protocol", instance->protocol_name)) { + FURI_LOG_E(TAG, "Unable to add Protocol"); + res = SubGhzProtocolStatusErrorParserProtocolName; + break; + } + + uint32_t temp_data = instance->id; + if(!flipper_format_write_uint32(flipper_format, "Id", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Id"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->data_count_bit; + if(!flipper_format_write_uint32(flipper_format, "Bit", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; + 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->data >> (i * 8)) & 0xFF; + } + + if(!flipper_format_write_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Unable to add Data"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->battery_low; + if(!flipper_format_write_uint32(flipper_format, "Batt", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->humidity; + if(!flipper_format_write_uint32(flipper_format, "Hum", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Humidity"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + //DATE AGE set + FuriHalRtcDateTime curr_dt; + furi_hal_rtc_get_datetime(&curr_dt); + uint32_t curr_ts = furi_hal_rtc_datetime_to_timestamp(&curr_dt); + + temp_data = curr_ts; + if(!flipper_format_write_uint32(flipper_format, "Ts", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->channel; + if(!flipper_format_write_uint32(flipper_format, "Ch", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Channel"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + temp_data = instance->btn; + if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) { + FURI_LOG_E(TAG, "Unable to add Btn"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + float temp = instance->temp; + if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) { + FURI_LOG_E(TAG, "Unable to add Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + res = SubGhzProtocolStatusOk; + } while(false); + furi_string_free(temp_str); + return res; +} + +SubGhzProtocolStatus + ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format) { + furi_assert(instance); + SubGhzProtocolStatus res = SubGhzProtocolStatusError; + uint32_t temp_data = 0; + + do { + if(!flipper_format_rewind(flipper_format)) { + FURI_LOG_E(TAG, "Rewind error"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + if(!flipper_format_read_uint32(flipper_format, "Id", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Id"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->id = (uint32_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Bit", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Bit"); + res = SubGhzProtocolStatusErrorParserBitCount; + break; + } + instance->data_count_bit = (uint8_t)temp_data; + + uint8_t key_data[sizeof(uint64_t)] = {0}; + if(!flipper_format_read_hex(flipper_format, "Data", key_data, sizeof(uint64_t))) { + FURI_LOG_E(TAG, "Missing Data"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + + for(uint8_t i = 0; i < sizeof(uint64_t); i++) { + instance->data = instance->data << 8 | key_data[i]; + } + + if(!flipper_format_read_uint32(flipper_format, "Batt", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Battery_low"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->battery_low = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Hum", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Humidity"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->humidity = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Ts", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing timestamp"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->timestamp = (uint32_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Ch", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Channel"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->channel = (uint8_t)temp_data; + + if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) { + FURI_LOG_E(TAG, "Missing Btn"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->btn = (uint8_t)temp_data; + + float temp; + if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) { + FURI_LOG_E(TAG, "Missing Temperature"); + res = SubGhzProtocolStatusErrorParserOthers; + break; + } + instance->temp = temp; + + res = SubGhzProtocolStatusOk; + } while(0); + + return res; +} + +SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit) { + SubGhzProtocolStatus ret = SubGhzProtocolStatusError; + do { + ret = ws_block_generic_deserialize(instance, flipper_format); + if(ret != SubGhzProtocolStatusOk) { + break; + } + if(instance->data_count_bit != count_bit) { + FURI_LOG_E(TAG, "Wrong number of bits in key"); + ret = SubGhzProtocolStatusErrorValueBitCount; + break; + } + } while(false); + return ret; +} \ No newline at end of file diff --git a/lib/subghz/protocols/ws_generic.h b/lib/subghz/protocols/ws_generic.h new file mode 100644 index 00000000000..288a4b9b399 --- /dev/null +++ b/lib/subghz/protocols/ws_generic.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#include +#include "furi.h" +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WS_NO_ID 0xFFFFFFFF +#define WS_NO_BATT 0xFF +#define WS_NO_HUMIDITY 0xFF +#define WS_NO_CHANNEL 0xFF +#define WS_NO_BTN 0xFF +#define WS_NO_TEMPERATURE -273.0f + +#define WS_KEY_FILE_VERSION 1 +#define WS_KEY_FILE_TYPE "Flipper SubGhz Key File" + +typedef struct WSBlockGeneric WSBlockGeneric; + +struct WSBlockGeneric { + const char* protocol_name; + uint64_t data; + uint32_t id; + uint8_t data_count_bit; + uint8_t battery_low; + uint8_t humidity; + uint32_t timestamp; + uint8_t channel; + uint8_t btn; + float temp; +}; + +/** + * Get name preset. + * @param preset_name name preset + * @param preset_str Output name preset + */ +void ws_block_generic_get_preset_name(const char* preset_name, FuriString* preset_str); + +/** + * Serialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_block_generic_serialize( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipper_format); + +/** + * Deserialize data WSBlockGeneric. + * @param instance Pointer to a WSBlockGeneric instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param count_bit Count bit protocol + * @return status + */ +SubGhzProtocolStatus ws_block_generic_deserialize_check_count_bit( + WSBlockGeneric* instance, + FlipperFormat* flipper_format, + uint16_t count_bit); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/subghz/protocols/x10.c b/lib/subghz/protocols/x10.c index fbc95b13e23..546fce55022 100644 --- a/lib/subghz/protocols/x10.c +++ b/lib/subghz/protocols/x10.c @@ -210,10 +210,10 @@ static void subghz_protocol_x10_check_remote_controller(SubGhzBlockGeneric* inst instance->btn = (((instance->data & 0x07000000) >> 24) | ((instance->data & 0xF800) >> 8)); } -uint8_t subghz_protocol_decoder_x10_get_hash_data(void* context) { +uint32_t subghz_protocol_decoder_x10_get_hash_data(void* context) { furi_assert(context); SubGhzProtocolDecoderX10* instance = context; - return subghz_protocol_blocks_get_hash_data( + return subghz_protocol_blocks_get_hash_data_long( &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); } diff --git a/lib/subghz/protocols/x10.h b/lib/subghz/protocols/x10.h index 5fc67bf69f7..3fc3ce110e2 100644 --- a/lib/subghz/protocols/x10.h +++ b/lib/subghz/protocols/x10.h @@ -52,7 +52,7 @@ bool subghz_protocol_x10_validate(void* context); * @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance * @return hash Hash sum */ -uint8_t subghz_protocol_decoder_x10_get_hash_data(void* context); +uint32_t subghz_protocol_decoder_x10_get_hash_data(void* context); /** * Serialize data SubGhzProtocolDecoderX10. diff --git a/lib/subghz/types.h b/lib/subghz/types.h index 0dc219bbfaa..ca059e551a3 100644 --- a/lib/subghz/types.h +++ b/lib/subghz/types.h @@ -37,6 +37,8 @@ typedef struct { uint32_t frequency; uint8_t* data; size_t data_size; + float latitude; + float longitude; } SubGhzRadioPreset; typedef enum { @@ -59,6 +61,8 @@ typedef enum { SubGhzProtocolStatusErrorEncoderGetUpload = (-12), ///< Payload encoder failure // Special Values SubGhzProtocolStatusErrorProtocolNotFound = (-13), ///< Protocol not found + SubGhzProtocolStatusErrorParserLatitude = (-14), ///< Missing `Latitude` + SubGhzProtocolStatusErrorParserLongitude = (-15), ///< Missing `Longitude` SubGhzProtocolStatusReserved = 0x7FFFFFFF, ///< Prevents enum down-size compiler optimization. } SubGhzProtocolStatus; @@ -74,7 +78,7 @@ typedef SubGhzProtocolStatus (*SubGhzDeserialize)(void* context, FlipperFormat* // Decoder specific typedef void (*SubGhzDecoderFeed)(void* decoder, bool level, uint32_t duration); typedef void (*SubGhzDecoderReset)(void* decoder); -typedef uint8_t (*SubGhzGetHashData)(void* decoder); +typedef uint32_t (*SubGhzGetHashData)(void* decoder); typedef void (*SubGhzGetString)(void* decoder, FuriString* output); // Encoder specific @@ -108,7 +112,7 @@ typedef enum { SubGhzProtocolTypeStatic, SubGhzProtocolTypeDynamic, SubGhzProtocolTypeRAW, - SubGhzProtocolWeatherStation, + SubGhzProtocolWeatherStation, // Unused, kept for compatibility SubGhzProtocolCustom, SubGhzProtocolTypeBinRAW, } SubGhzProtocolType; @@ -125,13 +129,18 @@ typedef enum { SubGhzProtocolFlag_Load = (1 << 8), SubGhzProtocolFlag_Send = (1 << 9), SubGhzProtocolFlag_BinRAW = (1 << 10), - SubGhzProtocolFlag_StarLine = (1 << 11), - SubGhzProtocolFlag_AutoAlarms = (1 << 12), - SubGhzProtocolFlag_Magellan = (1 << 13), - SubGhzProtocolFlag_Princeton = (1 << 14), - SubGhzProtocolFlag_NiceFlorS = (1 << 15), } SubGhzProtocolFlag; +typedef enum { + SubGhzProtocolFilter_StarLine = (1 << 0), + SubGhzProtocolFilter_AutoAlarms = (1 << 1), + SubGhzProtocolFilter_Magellan = (1 << 2), + SubGhzProtocolFilter_Princeton = (1 << 3), + SubGhzProtocolFilter_NiceFlorS = (1 << 4), + SubGhzProtocolFilter_Weather = (1 << 5), + SubGhzProtocolFilter_TPMS = (1 << 6), +} SubGhzProtocolFilter; + struct SubGhzProtocol { const char* name; SubGhzProtocolType type; @@ -139,4 +148,6 @@ struct SubGhzProtocol { const SubGhzProtocolEncoder* encoder; const SubGhzProtocolDecoder* decoder; + + SubGhzProtocolFilter filter; }; diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 927dcf8c942..964c66b076f 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -3193,6 +3193,7 @@ Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], size_t, uint8_t Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], size_t, uint8_t, uint8_t" Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_t" Function,+,subghz_protocol_blocks_get_hash_data,uint8_t,"SubGhzBlockDecoder*, size_t" +Function,+,subghz_protocol_blocks_get_hash_data_long,uint32_t,"SubGhzBlockDecoder*, size_t" Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t" Function,+,subghz_protocol_blocks_get_upload_from_bit_array,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t, SubGhzProtocolBlockAlignBit" Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], size_t, uint16_t, uint16_t" @@ -3206,6 +3207,7 @@ Function,+,subghz_protocol_blocks_xor_bytes,uint8_t,"const uint8_t[], size_t" Function,+,subghz_protocol_came_atomo_create_data,_Bool,"void*, FlipperFormat*, uint32_t, uint16_t, SubGhzRadioPreset*" Function,+,subghz_protocol_decoder_base_deserialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*" Function,+,subghz_protocol_decoder_base_get_hash_data,uint8_t,SubGhzProtocolDecoderBase* +Function,+,subghz_protocol_decoder_base_get_hash_data_long,uint32_t,SubGhzProtocolDecoderBase* Function,+,subghz_protocol_decoder_base_get_string,_Bool,"SubGhzProtocolDecoderBase*, FuriString*" Function,+,subghz_protocol_decoder_base_serialize,SubGhzProtocolStatus,"SubGhzProtocolDecoderBase*, FlipperFormat*, SubGhzRadioPreset*" Function,-,subghz_protocol_decoder_base_set_decoder_callback,void,"SubGhzProtocolDecoderBase*, SubGhzProtocolDecoderBaseRxCallback, void*"