Просмотр исходного кода

[FL-2920] WS: add protocol Acurite-606TX, LaCrosse_TX141THBv2 (#1898)

* WS: add protocol Acurite-606TX
* WS: history, added display of the channel (if any) in the general list
* WS: added display of the button state if it is on the transmitter, and displaying the data that is in the signal
* WS: fix batt info
* WS: add protocol LaCrosse_TX141THBv2
* WS; fix syntax
* Furi: bump api_symbols version

Co-authored-by: あく <alleteam@gmail.com>
Skorpionm 3 лет назад
Родитель
Сommit
c1bb10a694

+ 1 - 1
applications/plugins/weather_station/helpers/weather_station_types.h

@@ -3,7 +3,7 @@
 #include <furi.h>
 #include <furi.h>
 #include <furi_hal.h>
 #include <furi_hal.h>
 
 
-#define WS_VERSION_APP "0.1"
+#define WS_VERSION_APP "0.2"
 #define WS_DEVELOPED "SkorP"
 #define WS_DEVELOPED "SkorP"
 #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 #define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 
 

+ 252 - 0
applications/plugins/weather_station/protocols/acurite_606tx.c

@@ -0,0 +1,252 @@
+#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 = SubGhzProtocolWeatherStation,
+    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
+            SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
+
+    .decoder = &ws_protocol_acurite_606tx_decoder,
+    .encoder = &ws_protocol_acurite_606tx_encoder,
+};
+
+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) &&
+               (DURATION_DIFF(duration, ws_protocol_acurite_606tx_const.te_short) <
+                ws_protocol_acurite_606tx_const.te_delta)) {
+                //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;
+
+                break;
+            } else if(
+                (DURATION_DIFF(
+                     instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) <
+                 ws_protocol_acurite_606tx_const.te_delta) &&
+                (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(
+                     instance->decoder.te_last, ws_protocol_acurite_606tx_const.te_short) <
+                 ws_protocol_acurite_606tx_const.te_delta) &&
+                (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;
+        }
+        break;
+    }
+}
+
+uint8_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(
+        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
+}
+
+bool 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);
+}
+
+bool ws_protocol_decoder_acurite_606tx_deserialize(void* context, FlipperFormat* flipper_format) {
+    furi_assert(context);
+    WSProtocolDecoderAcurite_606TX* instance = context;
+    bool ret = false;
+    do {
+        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
+            break;
+        }
+        if(instance->generic.data_count_bit !=
+           ws_protocol_acurite_606tx_const.min_count_bit_for_found) {
+            FURI_LOG_E(TAG, "Wrong number of bits in key");
+            break;
+        }
+        ret = true;
+    } while(false);
+    return ret;
+}
+
+void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* output) {
+    furi_assert(context);
+    WSProtocolDecoderAcurite_606TX* instance = context;
+    furi_string_printf(
+        output,
+        "%s %dbit\r\n"
+        "Key:0x%lX%08lX\r\n"
+        "Sn:0x%lX Ch:%d  Bat:%d\r\n"
+        "Temp:%d.%d 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,
+        (int16_t)instance->generic.temp,
+        abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
+        instance->generic.humidity);
+}

+ 79 - 0
applications/plugins/weather_station/protocols/acurite_606tx.h

@@ -0,0 +1,79 @@
+#pragma once
+
+#include <lib/subghz/protocols/base.h>
+
+#include <lib/subghz/blocks/const.h>
+#include <lib/subghz/blocks/decoder.h>
+#include <lib/subghz/blocks/encoder.h>
+#include "ws_generic.h"
+#include <lib/subghz/blocks/math.h>
+
+#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
+ */
+uint8_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 true On success
+ */
+bool 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 true On success
+ */
+bool 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);

+ 1 - 0
applications/plugins/weather_station/protocols/infactory.c

@@ -142,6 +142,7 @@ static bool ws_protocol_infactory_check_crc(WSProtocolDecoderInfactory* instance
 static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) {
 static void ws_protocol_infactory_remote_controller(WSBlockGeneric* instance) {
     instance->id = instance->data >> 32;
     instance->id = instance->data >> 32;
     instance->battery_low = (instance->data >> 26) & 1;
     instance->battery_low = (instance->data >> 26) & 1;
+    instance->btn = WS_NO_BTN;
     instance->temp = ws_block_generic_fahrenheit_to_celsius(
     instance->temp = ws_block_generic_fahrenheit_to_celsius(
         ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
         ((float)((instance->data >> 12) & 0x0FFF) - 900.0f) / 10.0f);
     instance->humidity =
     instance->humidity =

+ 298 - 0
applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.c

@@ -0,0 +1,298 @@
+#include "lacrosse_tx141thbv2.h"
+
+#define TAG "WSProtocolLaCrosse_TX141THBv2"
+
+/*
+ * Help
+ * https://github.com/merbanan/rtl_433/blob/7e83cfd27d14247b6c3c81732bfe4a4f9a974d30/src/devices/lacrosse_tx141x.c
+ *  
+ *     iiii iiii | bkcc tttt | tttt tttt | hhhh hhhh | cccc cccc | u
+ * - 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 = 250,
+    .te_long = 500,
+    .te_delta = 120,
+    .min_count_bit_for_found = 41,
+};
+
+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 = SubGhzProtocolWeatherStation,
+    .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
+            SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
+
+    .decoder = &ws_protocol_lacrosse_tx141thbv2_decoder,
+    .encoder = &ws_protocol_lacrosse_tx141thbv2_encoder,
+};
+
+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;
+    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 crc = subghz_protocol_blocks_lfsr_digest8_reflect(msg, 4, 0x31, 0xF4);
+    return (crc == ((instance->decoder.decode_data >> 1) & 0xFF));
+}
+
+/**
+ * Analysis of received data
+ * @param instance Pointer to a WSBlockGeneric* instance
+ */
+static void ws_protocol_lacrosse_tx141thbv2_remote_controller(WSBlockGeneric* instance) {
+    instance->id = instance->data >> 33;
+    instance->battery_low = (instance->data >> 32) & 1;
+    instance->btn = (instance->data >> 31) & 1;
+    instance->channel = ((instance->data >> 29) & 0x03) + 1;
+    instance->temp = ((float)((instance->data >> 17) & 0x0FFF) - 500.0f) / 10.0f;
+    instance->humidity = (instance->data >> 9) & 0xFF;
+}
+
+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 * 3) <
+            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 * 3) <
+                ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2) &&
+               (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short * 3) <
+                ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2)) {
+                //Found preambule
+                instance->header_count++;
+            } else if(instance->header_count == 4) {
+                if((DURATION_DIFF(
+                        instance->decoder.te_last,
+                        ws_protocol_lacrosse_tx141thbv2_const.te_short) <
+                    ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
+                   (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
+                    ws_protocol_lacrosse_tx141thbv2_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 = LaCrosse_TX141THBv2DecoderStepSaveDuration;
+                } else if(
+                    (DURATION_DIFF(
+                         instance->decoder.te_last,
+                         ws_protocol_lacrosse_tx141thbv2_const.te_long) <
+                     ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
+                    (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
+                     ws_protocol_lacrosse_tx141thbv2_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 = 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 * 3) <
+                 ws_protocol_lacrosse_tx141thbv2_const.te_delta * 2))) {
+                if((instance->decoder.decode_count_bit ==
+                    ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) &&
+                   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(
+                (DURATION_DIFF(
+                     instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
+                 ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
+                (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
+                 ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 0);
+                instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
+            } else if(
+                (DURATION_DIFF(
+                     instance->decoder.te_last, ws_protocol_lacrosse_tx141thbv2_const.te_long) <
+                 ws_protocol_lacrosse_tx141thbv2_const.te_delta) &&
+                (DURATION_DIFF(duration, ws_protocol_lacrosse_tx141thbv2_const.te_short) <
+                 ws_protocol_lacrosse_tx141thbv2_const.te_delta)) {
+                subghz_protocol_blocks_add_bit(&instance->decoder, 1);
+                instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepSaveDuration;
+            } else {
+                instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
+            }
+        } else {
+            instance->decoder.parser_step = LaCrosse_TX141THBv2DecoderStepReset;
+        }
+        break;
+    }
+}
+
+uint8_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(
+        &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
+}
+
+bool 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);
+}
+
+bool ws_protocol_decoder_lacrosse_tx141thbv2_deserialize(
+    void* context,
+    FlipperFormat* flipper_format) {
+    furi_assert(context);
+    WSProtocolDecoderLaCrosse_TX141THBv2* instance = context;
+    bool ret = false;
+    do {
+        if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
+            break;
+        }
+        if(instance->generic.data_count_bit !=
+           ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found) {
+            FURI_LOG_E(TAG, "Wrong number of bits in key");
+            break;
+        }
+        ret = true;
+    } 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_printf(
+        output,
+        "%s %dbit\r\n"
+        "Key:0x%lX%08lX\r\n"
+        "Sn:0x%lX Ch:%d  Bat:%d\r\n"
+        "Temp:%d.%d 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,
+        (int16_t)instance->generic.temp,
+        abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
+        instance->generic.humidity);
+}

+ 81 - 0
applications/plugins/weather_station/protocols/lacrosse_tx141thbv2.h

@@ -0,0 +1,81 @@
+#pragma once
+
+#include <lib/subghz/protocols/base.h>
+
+#include <lib/subghz/blocks/const.h>
+#include <lib/subghz/blocks/decoder.h>
+#include <lib/subghz/blocks/encoder.h>
+#include "ws_generic.h"
+#include <lib/subghz/blocks/math.h>
+
+#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
+ */
+uint8_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 true On success
+ */
+bool 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 true On success
+ */
+bool 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);

+ 1 - 1
applications/plugins/weather_station/protocols/nexus_th.c

@@ -127,7 +127,7 @@ static void ws_protocol_nexus_th_remote_controller(WSBlockGeneric* instance) {
     instance->id = (instance->data >> 28) & 0xFF;
     instance->id = (instance->data >> 28) & 0xFF;
     instance->battery_low = !((instance->data >> 27) & 1);
     instance->battery_low = !((instance->data >> 27) & 1);
     instance->channel = ((instance->data >> 24) & 0x03) + 1;
     instance->channel = ((instance->data >> 24) & 0x03) + 1;
-
+    instance->btn = WS_NO_BTN;
     if(!((instance->data >> 23) & 1)) {
     if(!((instance->data >> 23) & 1)) {
         instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f;
         instance->temp = (float)((instance->data >> 12) & 0x07FF) / 10.0f;
     } else {
     } else {

+ 2 - 0
applications/plugins/weather_station/protocols/protocol_items.c

@@ -5,6 +5,8 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
     &ws_protocol_thermopro_tx4,
     &ws_protocol_thermopro_tx4,
     &ws_protocol_nexus_th,
     &ws_protocol_nexus_th,
     &ws_protocol_gt_wt_03,
     &ws_protocol_gt_wt_03,
+    &ws_protocol_acurite_606tx,
+    &ws_protocol_lacrosse_tx141thbv2,
 };
 };
 
 
 const SubGhzProtocolRegistry weather_station_protocol_registry = {
 const SubGhzProtocolRegistry weather_station_protocol_registry = {

+ 2 - 0
applications/plugins/weather_station/protocols/protocol_items.h

@@ -5,5 +5,7 @@
 #include "thermopro_tx4.h"
 #include "thermopro_tx4.h"
 #include "nexus_th.h"
 #include "nexus_th.h"
 #include "gt_wt_03.h"
 #include "gt_wt_03.h"
+#include "acurite_606tx.h"
+#include "lacrosse_tx141thbv2.h"
 
 
 extern const SubGhzProtocolRegistry weather_station_protocol_registry;
 extern const SubGhzProtocolRegistry weather_station_protocol_registry;

+ 10 - 10
applications/plugins/weather_station/protocols/ws_generic.c

@@ -105,11 +105,11 @@ bool ws_block_generic_serialize(
             break;
             break;
         }
         }
 
 
-        // temp_data = instance->btn;
-        // if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) {
-        //     FURI_LOG_E(TAG, "Unable to add Btn");
-        //     break;
-        // }
+        temp_data = instance->btn;
+        if(!flipper_format_write_uint32(flipper_format, "Btn", &temp_data, 1)) {
+            FURI_LOG_E(TAG, "Unable to add Btn");
+            break;
+        }
 
 
         float temp = instance->temp;
         float temp = instance->temp;
         if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) {
         if(!flipper_format_write_float(flipper_format, "Temp", &temp, 1)) {
@@ -174,11 +174,11 @@ bool ws_block_generic_deserialize(WSBlockGeneric* instance, FlipperFormat* flipp
         }
         }
         instance->channel = (uint8_t)temp_data;
         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");
-        //     break;
-        // }
-        // instance->btn = (uint8_t)temp_data;
+        if(!flipper_format_read_uint32(flipper_format, "Btn", (uint32_t*)&temp_data, 1)) {
+            FURI_LOG_E(TAG, "Missing Btn");
+            break;
+        }
+        instance->btn = (uint8_t)temp_data;
 
 
         float temp;
         float temp;
         if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) {
         if(!flipper_format_read_float(flipper_format, "Temp", (float*)&temp, 1)) {

+ 7 - 0
applications/plugins/weather_station/protocols/ws_generic.h

@@ -13,6 +13,13 @@
 extern "C" {
 extern "C" {
 #endif
 #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
+
 typedef struct WSBlockGeneric WSBlockGeneric;
 typedef struct WSBlockGeneric WSBlockGeneric;
 
 
 struct WSBlockGeneric {
 struct WSBlockGeneric {

+ 35 - 20
applications/plugins/weather_station/views/weather_station_receiver_info.c

@@ -4,7 +4,6 @@
 #include "../protocols/ws_generic.h"
 #include "../protocols/ws_generic.h"
 #include <input/input.h>
 #include <input/input.h>
 #include <gui/elements.h>
 #include <gui/elements.h>
-#include "math.h"
 
 
 #define abs(x) ((x) > 0 ? (x) : -(x))
 #define abs(x) ((x) > 0 ? (x) : -(x))
 
 
@@ -47,14 +46,26 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) {
         model->generic->data_count_bit);
         model->generic->data_count_bit);
     canvas_draw_str(canvas, 5, 8, buffer);
     canvas_draw_str(canvas, 5, 8, buffer);
 
 
-    snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel);
-    canvas_draw_str(canvas, 105, 8, buffer);
+    if(model->generic->channel != WS_NO_CHANNEL) {
+        snprintf(buffer, sizeof(buffer), "Ch: %01d", model->generic->channel);
+        canvas_draw_str(canvas, 105, 8, buffer);
+    }
+
+    if(model->generic->id != WS_NO_ID) {
+        snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id);
+        canvas_draw_str(canvas, 5, 20, buffer);
+    }
 
 
-    snprintf(buffer, sizeof(buffer), "Sn: 0x%02lX", model->generic->id);
-    canvas_draw_str(canvas, 5, 20, buffer);
+    if(model->generic->btn != WS_NO_BTN) {
+        snprintf(buffer, sizeof(buffer), "Btn: %01d", model->generic->btn);
+        canvas_draw_str(canvas, 62, 20, buffer);
+    }
 
 
-    snprintf(buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low"));
-    canvas_draw_str(canvas, 85, 20, buffer);
+    if(model->generic->battery_low != WS_NO_BATT) {
+        snprintf(
+            buffer, sizeof(buffer), "Batt: %s", (!model->generic->battery_low ? "ok" : "low"));
+        canvas_draw_str(canvas, 90, 20, buffer);
+    }
 
 
     snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data);
     snprintf(buffer, sizeof(buffer), "Data: 0x%llX", model->generic->data);
     canvas_draw_str(canvas, 5, 32, buffer);
     canvas_draw_str(canvas, 5, 32, buffer);
@@ -62,19 +73,23 @@ void ws_view_receiver_info_draw(Canvas* canvas, WSReceiverInfoModel* model) {
     elements_bold_rounded_frame(canvas, 2, 37, 123, 25);
     elements_bold_rounded_frame(canvas, 2, 37, 123, 25);
     canvas_set_font(canvas, FontPrimary);
     canvas_set_font(canvas, FontPrimary);
 
 
-    canvas_draw_icon(canvas, 13 + 5, 42, &I_Therm_7x16);
-    snprintf(
-        buffer,
-        sizeof(buffer),
-        "%3.2d.%d C",
-        (int16_t)model->generic->temp,
-        abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10))));
-    canvas_draw_str_aligned(canvas, 58 + 5, 46, AlignRight, AlignTop, buffer);
-    canvas_draw_circle(canvas, 50 + 5, 45, 1);
-
-    canvas_draw_icon(canvas, 70 + 5, 42, &I_Humid_10x15);
-    snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity);
-    canvas_draw_str(canvas, 86 + 5, 54, buffer);
+    if(model->generic->temp != WS_NO_TEMPERATURE) {
+        canvas_draw_icon(canvas, 18, 42, &I_Therm_7x16);
+        snprintf(
+            buffer,
+            sizeof(buffer),
+            "%3.2d.%d C",
+            (int16_t)model->generic->temp,
+            abs(((int16_t)(model->generic->temp * 10) - (((int16_t)model->generic->temp) * 10))));
+        canvas_draw_str_aligned(canvas, 63, 46, AlignRight, AlignTop, buffer);
+        canvas_draw_circle(canvas, 55, 45, 1);
+    }
+
+    if(model->generic->humidity != WS_NO_HUMIDITY) {
+        canvas_draw_icon(canvas, 75, 42, &I_Humid_10x15);
+        snprintf(buffer, sizeof(buffer), "%d%%", model->generic->humidity);
+        canvas_draw_str(canvas, 91, 54, buffer);
+    }
 }
 }
 
 
 bool ws_view_receiver_info_input(InputEvent* event, void* context) {
 bool ws_view_receiver_info_input(InputEvent* event, void* context) {

+ 12 - 13
applications/plugins/weather_station/weather_station_history.c

@@ -2,6 +2,7 @@
 #include <flipper_format/flipper_format_i.h>
 #include <flipper_format/flipper_format_i.h>
 #include <lib/toolbox/stream/stream.h>
 #include <lib/toolbox/stream/stream.h>
 #include <lib/subghz/receiver.h>
 #include <lib/subghz/receiver.h>
+#include "protocols/ws_generic.h"
 
 
 #include <furi.h>
 #include <furi.h>
 
 
@@ -224,20 +225,18 @@ WSHistoryStateAddKey
             for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
             for(uint8_t i = 0; i < sizeof(uint64_t); i++) {
                 data = (data << 8) | key_data[i];
                 data = (data << 8) | key_data[i];
             }
             }
-            if(!(uint32_t)(data >> 32)) {
-                furi_string_printf(
-                    item->item_str,
-                    "%s %lX",
-                    furi_string_get_cstr(instance->tmp_string),
-                    (uint32_t)(data & 0xFFFFFFFF));
-            } else {
-                furi_string_printf(
-                    item->item_str,
-                    "%s %lX%08lX",
-                    furi_string_get_cstr(instance->tmp_string),
-                    (uint32_t)(data >> 32),
-                    (uint32_t)(data & 0xFFFFFFFF));
+            uint32_t temp_data = 0;
+            if(!flipper_format_read_uint32(item->flipper_string, "Ch", (uint32_t*)&temp_data, 1)) {
+                FURI_LOG_E(TAG, "Missing Channel");
+                break;
+            }
+            if(temp_data != WS_NO_CHANNEL) {
+                furi_string_cat_printf(instance->tmp_string, " Ch:%X", (uint8_t)temp_data);
             }
             }
+
+            furi_string_printf(
+                item->item_str, "%s %llX", furi_string_get_cstr(instance->tmp_string), data);
+
         } while(false);
         } while(false);
         instance->last_index_write++;
         instance->last_index_write++;
         return WSHistoryStateAddKeyNewDada;
         return WSHistoryStateAddKeyNewDada;

+ 7 - 1
firmware/targets/f7/api_symbols.csv

@@ -1,5 +1,5 @@
 entry,status,name,type,params
 entry,status,name,type,params
-Version,+,3.5,,
+Version,+,3.6,,
 Header,+,applications/services/bt/bt_service/bt.h,,
 Header,+,applications/services/bt/bt_service/bt.h,,
 Header,+,applications/services/cli/cli.h,,
 Header,+,applications/services/cli/cli.h,,
 Header,+,applications/services/cli/cli_vcp.h,,
 Header,+,applications/services/cli/cli_vcp.h,,
@@ -2271,13 +2271,19 @@ Function,-,subghz_keystore_raw_encrypted_save,_Bool,"const char*, const char*, u
 Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
 Function,-,subghz_keystore_raw_get_data,_Bool,"const char*, size_t, uint8_t*, size_t"
 Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*"
 Function,-,subghz_keystore_save,_Bool,"SubGhzKeystore*, const char*, uint8_t*"
 Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t"
 Function,+,subghz_protocol_blocks_add_bit,void,"SubGhzBlockDecoder*, uint8_t"
+Function,+,subghz_protocol_blocks_crc16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t"
+Function,+,subghz_protocol_blocks_crc16lsb,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t"
 Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_crc4,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_crc7,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_crc8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
+Function,+,subghz_protocol_blocks_crc8le,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_get_bit_array,_Bool,"uint8_t[], size_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,uint8_t,"SubGhzBlockDecoder*, size_t"
 Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t"
 Function,+,subghz_protocol_blocks_get_parity,uint8_t,"uint64_t, uint8_t"
 Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t"
 Function,+,subghz_protocol_blocks_get_upload,size_t,"uint8_t[], size_t, LevelDuration*, size_t, uint32_t"
+Function,+,subghz_protocol_blocks_lfsr_digest16,uint16_t,"const uint8_t[], unsigned, uint16_t, uint16_t"
+Function,+,subghz_protocol_blocks_lfsr_digest8,uint8_t,"const uint8_t[], unsigned, uint8_t, uint8_t"
+Function,+,subghz_protocol_blocks_lfsr_digest8_reflect,uint8_t,"const uint8_t[], int, uint8_t, uint8_t"
 Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t"
 Function,+,subghz_protocol_blocks_reverse_key,uint64_t,"uint64_t, uint8_t"
 Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t"
 Function,+,subghz_protocol_blocks_set_bit_array,void,"_Bool, uint8_t[], size_t, size_t"
 Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*"
 Function,-,subghz_protocol_decoder_base_deserialize,_Bool,"SubGhzProtocolDecoderBase*, FlipperFormat*"

+ 137 - 0
lib/subghz/blocks/math.c

@@ -79,4 +79,141 @@ uint8_t subghz_protocol_blocks_crc8(
         }
         }
     }
     }
     return remainder;
     return remainder;
+}
+
+uint8_t subghz_protocol_blocks_crc8le(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint8_t polynomial,
+    uint8_t init) {
+    uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8);
+    unsigned byte, bit;
+    polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8);
+
+    for(byte = 0; byte < nBytes; ++byte) {
+        remainder ^= message[byte];
+        for(bit = 0; bit < 8; ++bit) {
+            if(remainder & 1) {
+                remainder = (remainder >> 1) ^ polynomial;
+            } else {
+                remainder = (remainder >> 1);
+            }
+        }
+    }
+    return remainder;
+}
+
+uint16_t subghz_protocol_blocks_crc16lsb(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint16_t polynomial,
+    uint16_t init) {
+    uint16_t remainder = init;
+    unsigned byte, bit;
+
+    for(byte = 0; byte < nBytes; ++byte) {
+        remainder ^= message[byte];
+        for(bit = 0; bit < 8; ++bit) {
+            if(remainder & 1) {
+                remainder = (remainder >> 1) ^ polynomial;
+            } else {
+                remainder = (remainder >> 1);
+            }
+        }
+    }
+    return remainder;
+}
+
+uint16_t subghz_protocol_blocks_crc16(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint16_t polynomial,
+    uint16_t init) {
+    uint16_t remainder = init;
+    unsigned byte, bit;
+
+    for(byte = 0; byte < nBytes; ++byte) {
+        remainder ^= message[byte] << 8;
+        for(bit = 0; bit < 8; ++bit) {
+            if(remainder & 0x8000) {
+                remainder = (remainder << 1) ^ polynomial;
+            } else {
+                remainder = (remainder << 1);
+            }
+        }
+    }
+    return remainder;
+}
+
+uint8_t subghz_protocol_blocks_lfsr_digest8(
+    uint8_t const message[],
+    unsigned bytes,
+    uint8_t gen,
+    uint8_t key) {
+    uint8_t sum = 0;
+    for(unsigned k = 0; k < bytes; ++k) {
+        uint8_t data = message[k];
+        for(int i = 7; i >= 0; --i) {
+            // XOR key into sum if data bit is set
+            if((data >> i) & 1) sum ^= key;
+
+            // roll the key right (actually the lsb is dropped here)
+            // and apply the gen (needs to include the dropped lsb as msb)
+            if(key & 1)
+                key = (key >> 1) ^ gen;
+            else
+                key = (key >> 1);
+        }
+    }
+    return sum;
+}
+
+uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
+    uint8_t const message[],
+    int bytes,
+    uint8_t gen,
+    uint8_t key) {
+    uint8_t sum = 0;
+    // Process message from last byte to first byte (reflected)
+    for(int k = bytes - 1; k >= 0; --k) {
+        uint8_t data = message[k];
+        // Process individual bits of each byte (reflected)
+        for(int i = 0; i < 8; ++i) {
+            // XOR key into sum if data bit is set
+            if((data >> i) & 1) {
+                sum ^= key;
+            }
+
+            // roll the key left (actually the lsb is dropped here)
+            // and apply the gen (needs to include the dropped lsb as msb)
+            if(key & 0x80)
+                key = (key << 1) ^ gen;
+            else
+                key = (key << 1);
+        }
+    }
+    return sum;
+}
+
+uint16_t subghz_protocol_blocks_lfsr_digest16(
+    uint8_t const message[],
+    unsigned bytes,
+    uint16_t gen,
+    uint16_t key) {
+    uint16_t sum = 0;
+    for(unsigned k = 0; k < bytes; ++k) {
+        uint8_t data = message[k];
+        for(int i = 7; i >= 0; --i) {
+            // if data bit is set then xor with key
+            if((data >> i) & 1) sum ^= key;
+
+            // roll the key right (actually the lsb is dropped here)
+            // and apply the gen (needs to include the dropped lsb as msb)
+            if(key & 1)
+                key = (key >> 1) ^ gen;
+            else
+                key = (key >> 1);
+        }
+    }
+    return sum;
 }
 }

+ 92 - 5
lib/subghz/blocks/math.h

@@ -19,7 +19,7 @@ extern "C" {
  * @param key In data
  * @param key In data
  * @param count_bit number of data bits
  * @param count_bit number of data bits
  * @return Reverse data
  * @return Reverse data
- */
+ **/
 uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit);
 uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit);
 
 
 /**
 /**
@@ -27,7 +27,7 @@ uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t count_bit);
  * @param key In data
  * @param key In data
  * @param count_bit number of data bits
  * @param count_bit number of data bits
  * @return parity
  * @return parity
- */
+ **/
 uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit);
 uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit);
 
 
 /**
 /**
@@ -37,7 +37,7 @@ uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t count_bit);
  * @param polynomial CRC polynomial
  * @param polynomial CRC polynomial
  * @param init starting crc value
  * @param init starting crc value
  * @return CRC value
  * @return CRC value
- */
+ **/
 uint8_t subghz_protocol_blocks_crc4(
 uint8_t subghz_protocol_blocks_crc4(
     uint8_t const message[],
     uint8_t const message[],
     unsigned nBytes,
     unsigned nBytes,
@@ -51,7 +51,7 @@ uint8_t subghz_protocol_blocks_crc4(
  * @param polynomial CRC polynomial
  * @param polynomial CRC polynomial
  * @param init starting crc value
  * @param init starting crc value
  * @return CRC value
  * @return CRC value
- */
+ **/
 uint8_t subghz_protocol_blocks_crc7(
 uint8_t subghz_protocol_blocks_crc7(
     uint8_t const message[],
     uint8_t const message[],
     unsigned nBytes,
     unsigned nBytes,
@@ -67,13 +67,100 @@ uint8_t subghz_protocol_blocks_crc7(
  * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one)
  * @param polynomial byte is from x^7 to x^0 (x^8 is implicitly one)
  * @param init starting crc value
  * @param init starting crc value
  * @return CRC value
  * @return CRC value
- */
+ **/
 uint8_t subghz_protocol_blocks_crc8(
 uint8_t subghz_protocol_blocks_crc8(
     uint8_t const message[],
     uint8_t const message[],
     unsigned nBytes,
     unsigned nBytes,
     uint8_t polynomial,
     uint8_t polynomial,
     uint8_t init);
     uint8_t init);
 
 
+/**
+ * "Little-endian" Cyclic Redundancy Check CRC-8 LE
+ *  Input and output are reflected, i.e. least significant bit is shifted in first.
+ *  @param message array of bytes to check
+ *  @param nBytes number of bytes in message
+ *  @param polynomial CRC polynomial
+ *  @param init starting crc value
+ *  @return CRC value
+ **/
+uint8_t subghz_protocol_blocks_crc8le(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint8_t polynomial,
+    uint8_t init);
+
+/**
+ *  CRC-16 LSB.
+ *  Input and output are reflected, i.e. least significant bit is shifted in first.
+ *  Note that poly and init already need to be reflected.
+ *  @param message array of bytes to check
+ *  @param nBytes number of bytes in message
+ *  @param polynomial CRC polynomial
+ *  @param init starting crc value
+ *  @return CRC value
+ **/
+uint16_t subghz_protocol_blocks_crc16lsb(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint16_t polynomial,
+    uint16_t init);
+
+/**
+ * CRC-16.
+ *  @param message array of bytes to check
+ *  @param nBytes number of bytes in message
+ *  @param polynomial CRC polynomial
+ *  @param init starting crc value
+ *  @return CRC value
+ **/
+uint16_t subghz_protocol_blocks_crc16(
+    uint8_t const message[],
+    unsigned nBytes,
+    uint16_t polynomial,
+    uint16_t init);
+
+/**
+ *  Digest-8 by "LFSR-based Toeplitz hash".
+ *  @param message bytes of message data
+ *  @param bytes number of bytes to digest
+ *  @param gen key stream generator, needs to includes the MSB if the LFSR is rolling
+ *  @param key initial key
+ *  @return digest value
+ **/
+uint8_t subghz_protocol_blocks_lfsr_digest8(
+    uint8_t const message[],
+    unsigned bytes,
+    uint8_t gen,
+    uint8_t key);
+
+/**
+ *  Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect.
+ *  @param message bytes of message data
+ *  @param bytes number of bytes to digest
+ *  @param gen key stream generator, needs to includes the MSB if the LFSR is rolling
+ *  @param key initial key
+ *  @return digest value
+ **/
+uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
+    uint8_t const message[],
+    int bytes,
+    uint8_t gen,
+    uint8_t key);
+
+/**
+ *  Digest-16 by "LFSR-based Toeplitz hash".
+ *  @param message bytes of message data
+ *  @param bytes number of bytes to digest
+ *  @param gen key stream generator, needs to includes the MSB if the LFSR is rolling
+ *  @param key initial key
+ *  @return digest value
+ **/
+uint16_t subghz_protocol_blocks_lfsr_digest16(
+    uint8_t const message[],
+    unsigned bytes,
+    uint16_t gen,
+    uint16_t key);
+
 #ifdef __cplusplus
 #ifdef __cplusplus
 }
 }
 #endif
 #endif