mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-07-27 01:58:09 -07:00
Merge branch 'dev' of https://github.com/Flipper-XFW/Xtreme-Firmware into dev
This commit is contained in:
@@ -110,9 +110,9 @@ void t5577_write_with_pass(LFRFIDT5577* data, uint32_t password) {
|
||||
t5577_start();
|
||||
FURI_CRITICAL_ENTER();
|
||||
for(size_t i = 0; i < data->blocks_to_write; i++) {
|
||||
t5577_write_block_pass(0, false, data->block[i], true, password);
|
||||
t5577_write_block_pass(i, false, data->block[i], true, password);
|
||||
}
|
||||
t5577_write_reset();
|
||||
FURI_CRITICAL_EXIT();
|
||||
t5577_stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &ws_protocol_acurite_592txr_decoder,
|
||||
.encoder = &ws_protocol_acurite_592txr_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,240 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.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) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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 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);
|
||||
@@ -0,0 +1,240 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_acurite_609txc_decoder,
|
||||
.encoder = &ws_protocol_acurite_609txc_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,269 @@
|
||||
#include "ambient_weather.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_ambient_weather_decoder,
|
||||
.encoder = &ws_protocol_ambient_weather_encoder,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,255 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_auriol_ahfl_decoder,
|
||||
.encoder = &ws_protocol_auriol_ahfl_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,249 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_auriol_th_decoder,
|
||||
.encoder = &ws_protocol_auriol_th_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,256 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_gt_wt_02_decoder,
|
||||
.encoder = &ws_protocol_gt_wt_02_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,331 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_gt_wt_03_decoder,
|
||||
.encoder = &ws_protocol_gt_wt_03_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,287 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_infactory_decoder,
|
||||
.encoder = &ws_protocol_infactory_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_infactory_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderInfactory* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,319 @@
|
||||
#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_Weather,
|
||||
|
||||
.decoder = &ws_protocol_lacrosse_tx_decoder,
|
||||
.encoder = &ws_protocol_lacrosse_tx_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,295 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
return ws_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
ws_protocol_lacrosse_tx141thbv2_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -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 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);
|
||||
@@ -0,0 +1,364 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &ws_protocol_nexus_th_decoder,
|
||||
.encoder = &ws_protocol_nexus_th_encoder,
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,430 @@
|
||||
#include "oregon2.h"
|
||||
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include "ws_generic.h"
|
||||
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_oregon2_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderOregon2* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&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_Weather | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_oregon2_decoder,
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define WS_PROTOCOL_OREGON2_NAME "Oregon2"
|
||||
extern const SubGhzProtocol ws_protocol_oregon2;
|
||||
@@ -0,0 +1,366 @@
|
||||
#include "oregon3.h"
|
||||
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include "ws_generic.h"
|
||||
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_oregon3_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderOregon3* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&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_Weather | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_oregon3_decoder,
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define WS_PROTOCOL_OREGON3_NAME "Oregon3"
|
||||
extern const SubGhzProtocol ws_protocol_oregon3;
|
||||
@@ -0,0 +1,322 @@
|
||||
#include "oregon_v1.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_oregon_v1_decoder,
|
||||
.encoder = &ws_protocol_oregon_v1_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "pcsg_generic.h"
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
|
||||
#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_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;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "furi.h"
|
||||
#include "furi_hal.h"
|
||||
#include <lib/subghz/types.h>
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,383 @@
|
||||
#include "pocsag.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
#include <furi/core/string.h>
|
||||
|
||||
#define TAG "POCSAG"
|
||||
|
||||
static const SubGhzBlockConst pocsag_const = {
|
||||
.te_short = 833,
|
||||
.te_delta = 100,
|
||||
};
|
||||
|
||||
// Minimal amount of sync bits (interleaving zeros and ones)
|
||||
#define POCSAG_MIN_SYNC_BITS 32
|
||||
#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;
|
||||
};
|
||||
|
||||
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();
|
||||
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);
|
||||
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) {
|
||||
// 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 / pocsag_const.te_short;
|
||||
uint32_t extra = duration - pocsag_const.te_short * bits_count;
|
||||
|
||||
if(DURATION_DIFF(extra, pocsag_const.te_short) < pocsag_const.te_delta)
|
||||
bits_count++;
|
||||
else if(extra > pocsag_const.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_pocsag_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderPocsag* instance = context;
|
||||
uint8_t hash = 0;
|
||||
for(size_t i = 0; i < furi_string_size(instance->done_msg); i++)
|
||||
hash ^= furi_string_get_char(instance->done_msg, i);
|
||||
return hash;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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\r\n", instance->generic.protocol_name);
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
#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 "pcsg_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_POCSAG_NAME "POCSAG"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_pocsag;
|
||||
@@ -42,6 +42,26 @@ const SubGhzProtocol* subghz_protocol_registry_items[] = {
|
||||
&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_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,
|
||||
&subghz_protocol_pocsag,
|
||||
&tpms_protocol_schrader_gg4,
|
||||
&subghz_protocol_bin_raw,
|
||||
};
|
||||
|
||||
|
||||
@@ -43,4 +43,24 @@
|
||||
#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 "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 "pocsag.h"
|
||||
#include "schrader_gg4.h"
|
||||
#include "bin_raw.h"
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "schrader_gg4.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "Schrader"
|
||||
|
||||
// https://github.com/merbanan/rtl_433/blob/master/src/devices/schraeder.c
|
||||
// https://elib.dlr.de/81155/1/TPMS_for_Trafffic_Management_purposes.pdf
|
||||
// https://github.com/furrtek/portapack-havoc/issues/349
|
||||
// https://fccid.io/MRXGG4
|
||||
// https://fccid.io/MRXGG4T
|
||||
|
||||
/**
|
||||
* Schrader 3013/3015 MRX-GG4
|
||||
|
||||
OEM:
|
||||
KIA Sportage CGA 11-SPT1504-RA
|
||||
Mercedes-Benz A0009054100
|
||||
|
||||
* Frequency: 433.92MHz+-38KHz
|
||||
* Modulation: ASK
|
||||
* Working Temperature: -50°C to 125°C
|
||||
* Tire monitoring range value: 0kPa-350kPa+-7kPa
|
||||
|
||||
Examples in normal environmental conditions:
|
||||
3000878456094cd0
|
||||
3000878456084ecb
|
||||
3000878456074d01
|
||||
|
||||
Data layout:
|
||||
* | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
|
||||
* | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
|
||||
* | SSSS SSSS | IIII IIII | IIII IIII | IIII IIII | IIII IIII | PPPP PPPP | TTTT TTTT | CCCC CCCC |
|
||||
*
|
||||
|
||||
- The preamble is 0b000
|
||||
- S: always 0x30 in relearn state
|
||||
- I: 32 bit ID
|
||||
- P: 8 bit Pressure (multiplyed by 2.5 = PSI)
|
||||
- T: 8 bit Temperature (deg. C offset by 50)
|
||||
- C: 8 bit Checksum (CRC8, Poly 0x7, Init 0x0)
|
||||
*/
|
||||
|
||||
#define PREAMBLE 0b000
|
||||
#define PREAMBLE_BITS_LEN 3
|
||||
|
||||
static const SubGhzBlockConst tpms_protocol_schrader_gg4_const = {
|
||||
.te_short = 120,
|
||||
.te_long = 240,
|
||||
.te_delta = 55, // 50% of te_short due to poor sensitivity
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct TPMSProtocolDecoderSchraderGG4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
TPMSBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_saved_state;
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct TPMSProtocolEncoderSchraderGG4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
TPMSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
SchraderGG4DecoderStepReset = 0,
|
||||
SchraderGG4DecoderStepCheckPreamble,
|
||||
SchraderGG4DecoderStepDecoderData,
|
||||
SchraderGG4DecoderStepSaveDuration,
|
||||
SchraderGG4DecoderStepCheckDuration,
|
||||
} SchraderGG4DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder tpms_protocol_schrader_gg4_decoder = {
|
||||
.alloc = tpms_protocol_decoder_schrader_gg4_alloc,
|
||||
.free = tpms_protocol_decoder_schrader_gg4_free,
|
||||
|
||||
.feed = tpms_protocol_decoder_schrader_gg4_feed,
|
||||
.reset = tpms_protocol_decoder_schrader_gg4_reset,
|
||||
|
||||
.get_hash_data = 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_TPMS | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &tpms_protocol_schrader_gg4_decoder,
|
||||
.encoder = &tpms_protocol_schrader_gg4_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t tpms_protocol_decoder_schrader_gg4_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
TPMSProtocolDecoderSchraderGG4* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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 "tpms_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,252 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_thermopro_tx4_decoder,
|
||||
.encoder = &ws_protocol_thermopro_tx4_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,227 @@
|
||||
#include "tpms_generic.h"
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
|
||||
#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_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;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "furi.h"
|
||||
#include <furi_hal.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <locale/locale.h>
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,285 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_tx_8300_decoder,
|
||||
.encoder = &ws_protocol_tx_8300_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_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(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,300 @@
|
||||
#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_Weather |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
|
||||
.decoder = &ws_protocol_wendox_w6726_decoder,
|
||||
.encoder = &ws_protocol_wendox_w6726_encoder,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_wendox_w6726_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderWendoxW6726* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&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);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#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_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
|
||||
*/
|
||||
uint8_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);
|
||||
@@ -0,0 +1,256 @@
|
||||
#include "ws_generic.h"
|
||||
#include <lib/toolbox/stream/stream.h>
|
||||
#include <lib/flipper_format/flipper_format_i.h>
|
||||
//#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, "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_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;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
#include "furi.h"
|
||||
#include <furi_hal.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <locale/locale.h>
|
||||
|
||||
#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
|
||||
+2
-1
@@ -112,7 +112,6 @@ typedef enum {
|
||||
SubGhzProtocolTypeStatic,
|
||||
SubGhzProtocolTypeDynamic,
|
||||
SubGhzProtocolTypeRAW,
|
||||
SubGhzProtocolWeatherStation,
|
||||
SubGhzProtocolCustom,
|
||||
SubGhzProtocolTypeBinRAW,
|
||||
} SubGhzProtocolType;
|
||||
@@ -133,6 +132,8 @@ typedef enum {
|
||||
SubGhzProtocolFlag_AutoAlarms = (1 << 12),
|
||||
SubGhzProtocolFlag_Magellan = (1 << 13),
|
||||
SubGhzProtocolFlag_Princeton = (1 << 14),
|
||||
SubGhzProtocolFlag_Weather = (1 << 15),
|
||||
SubGhzProtocolFlag_TPMS = (1 << 16),
|
||||
} SubGhzProtocolFlag;
|
||||
|
||||
struct SubGhzProtocol {
|
||||
|
||||
Reference in New Issue
Block a user