Skip to content

Commit fdabd12

Browse files
authored
WS: add protocol Kedsum-TH/Conrad-S3318P (#104)
1 parent 11c4d2a commit fdabd12

File tree

7 files changed

+390
-21
lines changed

7 files changed

+390
-21
lines changed

weather_station/.catalog/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ GT-WT02
1111
GT-WT03
1212
Acurite 592TXR
1313
Acurite-606TX
14-
Acurite-606TXC
14+
Acurite-609TXC
1515
Acurite-986
1616
LaCrosse_TX
1717
TX141THBv2
@@ -23,3 +23,4 @@ Avriol HG06061
2323
TX8300
2424
Wendox W6276
2525
Auriol AHFL
26+
Kedsum-TH/Conrad-S3318P

weather_station/.catalog/changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 1.3
2+
- Add protocol Kedsum-TH/Conrad-S3318P
13
## 1.2
24
- Add protocol Acurite-986
35
- Minimal changes for recent API updates

weather_station/application.fam

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ App(
77
requires=["gui"],
88
stack_size=4 * 1024,
99
fap_description="Receive weather data from a wide range of supported Sub-1GHz remote sensor",
10-
fap_version="1.2",
10+
fap_version="1.3",
1111
fap_icon="weather_station_10px.png",
1212
fap_category="Sub-GHz",
1313
fap_icon_assets="images",

weather_station/protocols/kedsum_th.c

+294
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
#include "kedsum_th.h"
2+
3+
#define TAG "WSProtocolKedsumTH"
4+
5+
/*
6+
* Help
7+
* https://github.com/merbanan/rtl_433/blob/master/src/devices/kedsum.c
8+
*
9+
* Frame structure:
10+
*
11+
* Byte: 0 1 2 3 4
12+
* Nibble: 1 2 3 4 5 6 7 8 9 10
13+
* Type: 00 IIIIIIII BBCC++++ ttttTTTT hhhhHHHH FFFFXXXX
14+
*
15+
* - I: unique id. changes on powercycle
16+
* - B: Battery state 10 = Ok, 01 = weak, 00 = bad
17+
* - C: channel, 00 = ch1, 10=ch3
18+
* - + low temp nibble
19+
* - t: med temp nibble
20+
* - T: high temp nibble
21+
* - h: humidity low nibble
22+
* - H: humidity high nibble
23+
* - F: flags
24+
* - X: CRC-4 poly 0x3 init 0x0 xor last 4 bits
25+
*/
26+
27+
static const SubGhzBlockConst ws_protocol_kedsum_th_const = {
28+
.te_short = 500,
29+
.te_long = 2000,
30+
.te_delta = 150,
31+
.min_count_bit_for_found = 42,
32+
};
33+
34+
struct WSProtocolDecoderKedsumTH {
35+
SubGhzProtocolDecoderBase base;
36+
37+
SubGhzBlockDecoder decoder;
38+
WSBlockGeneric generic;
39+
40+
uint16_t header_count;
41+
};
42+
43+
struct WSProtocolEncoderKedsumTH {
44+
SubGhzProtocolEncoderBase base;
45+
46+
SubGhzProtocolBlockEncoder encoder;
47+
WSBlockGeneric generic;
48+
};
49+
50+
typedef enum {
51+
KedsumTHDecoderStepReset = 0,
52+
KedsumTHDecoderStepCheckPreambule,
53+
KedsumTHDecoderStepSaveDuration,
54+
KedsumTHDecoderStepCheckDuration,
55+
} KedsumTHDecoderStep;
56+
57+
const SubGhzProtocolDecoder ws_protocol_kedsum_th_decoder = {
58+
.alloc = ws_protocol_decoder_kedsum_th_alloc,
59+
.free = ws_protocol_decoder_kedsum_th_free,
60+
61+
.feed = ws_protocol_decoder_kedsum_th_feed,
62+
.reset = ws_protocol_decoder_kedsum_th_reset,
63+
64+
.get_hash_data = ws_protocol_decoder_kedsum_th_get_hash_data,
65+
.serialize = ws_protocol_decoder_kedsum_th_serialize,
66+
.deserialize = ws_protocol_decoder_kedsum_th_deserialize,
67+
.get_string = ws_protocol_decoder_kedsum_th_get_string,
68+
};
69+
70+
const SubGhzProtocolEncoder ws_protocol_kedsum_th_encoder = {
71+
.alloc = NULL,
72+
.free = NULL,
73+
74+
.deserialize = NULL,
75+
.stop = NULL,
76+
.yield = NULL,
77+
};
78+
79+
const SubGhzProtocol ws_protocol_kedsum_th = {
80+
.name = WS_PROTOCOL_KEDSUM_TH_NAME,
81+
.type = SubGhzProtocolWeatherStation,
82+
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
83+
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
84+
85+
.decoder = &ws_protocol_kedsum_th_decoder,
86+
.encoder = &ws_protocol_kedsum_th_encoder,
87+
};
88+
89+
void* ws_protocol_decoder_kedsum_th_alloc(SubGhzEnvironment* environment) {
90+
UNUSED(environment);
91+
WSProtocolDecoderKedsumTH* instance = malloc(sizeof(WSProtocolDecoderKedsumTH));
92+
instance->base.protocol = &ws_protocol_kedsum_th;
93+
instance->generic.protocol_name = instance->base.protocol->name;
94+
return instance;
95+
}
96+
97+
void ws_protocol_decoder_kedsum_th_free(void* context) {
98+
furi_assert(context);
99+
WSProtocolDecoderKedsumTH* instance = context;
100+
free(instance);
101+
}
102+
103+
void ws_protocol_decoder_kedsum_th_reset(void* context) {
104+
furi_assert(context);
105+
WSProtocolDecoderKedsumTH* instance = context;
106+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
107+
}
108+
109+
static bool ws_protocol_kedsum_th_check_crc(WSProtocolDecoderKedsumTH* instance) {
110+
uint8_t msg[] = {
111+
instance->decoder.decode_data >> 32,
112+
instance->decoder.decode_data >> 24,
113+
instance->decoder.decode_data >> 16,
114+
instance->decoder.decode_data >> 8,
115+
instance->decoder.decode_data};
116+
117+
uint8_t crc =
118+
subghz_protocol_blocks_crc4(msg, 4, 0x03, 0); // CRC-4 poly 0x3 init 0x0 xor last 4 bits
119+
crc ^= msg[4] >> 4; // last nibble is only XORed
120+
return (crc == (msg[4] & 0x0F));
121+
}
122+
123+
/**
124+
* Analysis of received data
125+
* @param instance Pointer to a WSBlockGeneric* instance
126+
*/
127+
static void ws_protocol_kedsum_th_remote_controller(WSBlockGeneric* instance) {
128+
instance->id = instance->data >> 32;
129+
if((instance->data >> 30) & 0x3) {
130+
instance->battery_low = 0;
131+
} else {
132+
instance->battery_low = 1;
133+
}
134+
instance->channel = ((instance->data >> 28) & 0x3) + 1;
135+
instance->btn = WS_NO_BTN;
136+
uint16_t temp_raw = ((instance->data >> 16) & 0x0f) << 8 |
137+
((instance->data >> 20) & 0x0f) << 4 | ((instance->data >> 24) & 0x0f);
138+
instance->temp = locale_fahrenheit_to_celsius(((float)temp_raw - 900.0f) / 10.0f);
139+
instance->humidity = ((instance->data >> 8) & 0x0f) << 4 | ((instance->data >> 12) & 0x0f);
140+
}
141+
142+
void ws_protocol_decoder_kedsum_th_feed(void* context, bool level, uint32_t duration) {
143+
furi_assert(context);
144+
WSProtocolDecoderKedsumTH* instance = context;
145+
146+
switch(instance->decoder.parser_step) {
147+
case KedsumTHDecoderStepReset:
148+
if((level) && (DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_short) <
149+
ws_protocol_kedsum_th_const.te_delta)) {
150+
instance->decoder.parser_step = KedsumTHDecoderStepCheckPreambule;
151+
instance->decoder.te_last = duration;
152+
instance->header_count = 0;
153+
}
154+
break;
155+
156+
case KedsumTHDecoderStepCheckPreambule:
157+
if(level) {
158+
instance->decoder.te_last = duration;
159+
} else {
160+
if((DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
161+
ws_protocol_kedsum_th_const.te_delta) &&
162+
(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 4) <
163+
ws_protocol_kedsum_th_const.te_delta * 4)) {
164+
//Found preambule
165+
instance->header_count++;
166+
} else if(
167+
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
168+
ws_protocol_kedsum_th_const.te_delta) &&
169+
(duration < (ws_protocol_kedsum_th_const.te_long * 2 +
170+
ws_protocol_kedsum_th_const.te_delta * 2))) {
171+
//Found syncPrefix
172+
if(instance->header_count > 0) {
173+
instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration;
174+
instance->decoder.decode_data = 0;
175+
instance->decoder.decode_count_bit = 0;
176+
if((DURATION_DIFF(
177+
instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
178+
ws_protocol_kedsum_th_const.te_delta) &&
179+
(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long) <
180+
ws_protocol_kedsum_th_const.te_delta * 2)) {
181+
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
182+
instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration;
183+
} else if(
184+
(DURATION_DIFF(
185+
instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
186+
ws_protocol_kedsum_th_const.te_delta) &&
187+
(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 2) <
188+
ws_protocol_kedsum_th_const.te_delta * 4)) {
189+
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
190+
instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration;
191+
} else {
192+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
193+
}
194+
}
195+
} else {
196+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
197+
}
198+
}
199+
break;
200+
201+
case KedsumTHDecoderStepSaveDuration:
202+
if(level) {
203+
instance->decoder.te_last = duration;
204+
instance->decoder.parser_step = KedsumTHDecoderStepCheckDuration;
205+
} else {
206+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
207+
}
208+
break;
209+
210+
case KedsumTHDecoderStepCheckDuration:
211+
if(!level) {
212+
if(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 4) <
213+
ws_protocol_kedsum_th_const.te_delta * 4) {
214+
//Found syncPostfix
215+
if((instance->decoder.decode_count_bit ==
216+
ws_protocol_kedsum_th_const.min_count_bit_for_found) &&
217+
ws_protocol_kedsum_th_check_crc(instance)) {
218+
instance->generic.data = instance->decoder.decode_data;
219+
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
220+
ws_protocol_kedsum_th_remote_controller(&instance->generic);
221+
if(instance->base.callback)
222+
instance->base.callback(&instance->base, instance->base.context);
223+
}
224+
instance->decoder.decode_data = 0;
225+
instance->decoder.decode_count_bit = 0;
226+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
227+
break;
228+
} else if(
229+
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
230+
ws_protocol_kedsum_th_const.te_delta) &&
231+
(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long) <
232+
ws_protocol_kedsum_th_const.te_delta * 2)) {
233+
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
234+
instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration;
235+
} else if(
236+
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_kedsum_th_const.te_short) <
237+
ws_protocol_kedsum_th_const.te_delta) &&
238+
(DURATION_DIFF(duration, ws_protocol_kedsum_th_const.te_long * 2) <
239+
ws_protocol_kedsum_th_const.te_delta * 4)) {
240+
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
241+
instance->decoder.parser_step = KedsumTHDecoderStepSaveDuration;
242+
} else {
243+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
244+
}
245+
} else {
246+
instance->decoder.parser_step = KedsumTHDecoderStepReset;
247+
}
248+
break;
249+
}
250+
}
251+
252+
uint8_t ws_protocol_decoder_kedsum_th_get_hash_data(void* context) {
253+
furi_assert(context);
254+
WSProtocolDecoderKedsumTH* instance = context;
255+
return subghz_protocol_blocks_get_hash_data(
256+
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
257+
}
258+
259+
SubGhzProtocolStatus ws_protocol_decoder_kedsum_th_serialize(
260+
void* context,
261+
FlipperFormat* flipper_format,
262+
SubGhzRadioPreset* preset) {
263+
furi_assert(context);
264+
WSProtocolDecoderKedsumTH* instance = context;
265+
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
266+
}
267+
268+
SubGhzProtocolStatus
269+
ws_protocol_decoder_kedsum_th_deserialize(void* context, FlipperFormat* flipper_format) {
270+
furi_assert(context);
271+
WSProtocolDecoderKedsumTH* instance = context;
272+
return ws_block_generic_deserialize_check_count_bit(
273+
&instance->generic, flipper_format, ws_protocol_kedsum_th_const.min_count_bit_for_found);
274+
}
275+
276+
void ws_protocol_decoder_kedsum_th_get_string(void* context, FuriString* output) {
277+
furi_assert(context);
278+
WSProtocolDecoderKedsumTH* instance = context;
279+
furi_string_printf(
280+
output,
281+
"%s %dbit\r\n"
282+
"Key:0x%lX%08lX\r\n"
283+
"Sn:0x%lX Ch:%d Bat:%d\r\n"
284+
"Temp:%3.1f C Hum:%d%%",
285+
instance->generic.protocol_name,
286+
instance->generic.data_count_bit,
287+
(uint32_t)(instance->generic.data >> 32),
288+
(uint32_t)(instance->generic.data),
289+
instance->generic.id,
290+
instance->generic.channel,
291+
instance->generic.battery_low,
292+
(double)instance->generic.temp,
293+
instance->generic.humidity);
294+
}

0 commit comments

Comments
 (0)