Skip to content

Commit 88d65f7

Browse files
paul759skotopes
andauthored
Added Acurite 5n1 Weather Station (#193)
Co-authored-by: あく <alleteam@gmail.com>
1 parent cb15cbd commit 88d65f7

File tree

7 files changed

+389
-2
lines changed

7 files changed

+389
-2
lines changed

weather_station/.catalog/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ThermoPRO-TX4
99
Nexus-TH
1010
GT-WT02
1111
GT-WT03
12+
Acurite 5n1
1213
Acurite 592TXR
1314
Acurite-606TX
1415
Acurite-609TXC

weather_station/.catalog/changelog.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
## 1.6
2+
- Add protocol Acurite 5n1 Weather Station
13
## 1.5
24
- Update API
35
## 1.4

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.5",
10+
fap_version="1.6",
1111
fap_icon="weather_station_10px.png",
1212
fap_category="Sub-GHz",
1313
fap_icon_assets="images",
+303
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
#include "acurite_5n1.h"
2+
3+
#define TAG "WSProtocolAcurite_5n1"
4+
5+
/*
6+
* Help
7+
* https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c
8+
*
9+
* Acurite 5n1 Wind Speed Temperature Humidity sensor decoder
10+
* Message Type 0x38, 8 bytes
11+
* | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
12+
* | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- |
13+
* | CCII IIII | IIII IIII | pB11 1000 | p??W WWWW | pWWW TTTT | pTTT TTTT | pHHH HHHH | KKKK KKKK |
14+
* - C: Channel 00: C, 10: B, 11: A, (01 is invalid)
15+
* - I: Device ID (14 bits)
16+
* - B: Battery, 1 is battery OK, 0 is battery low
17+
* - M: Message type (6 bits), 0x38
18+
* - W: Wind Speed (8 bits)
19+
* - T: Temperature Fahrenheit (11 bits), + 400 * 10
20+
* - H: Relative Humidity (%) (7 bits)
21+
* - K: Checksum (8 bits)
22+
* - p: Parity bit
23+
* Notes:
24+
* - Temperature
25+
* - Encoded as Fahrenheit + 400 * 10
26+
* - only 11 bits needed for specified range -40 F - 158 F (-40 C to 70 C)
27+
*
28+
*/
29+
30+
static const SubGhzBlockConst ws_protocol_acurite_5n1_const = {
31+
.te_short = 200,
32+
.te_long = 400,
33+
.te_delta = 90,
34+
.min_count_bit_for_found = 64,
35+
};
36+
37+
struct WSProtocolDecoderAcurite_5n1 {
38+
SubGhzProtocolDecoderBase base;
39+
40+
SubGhzBlockDecoder decoder;
41+
WSBlockGeneric generic;
42+
43+
uint16_t header_count;
44+
};
45+
46+
struct WSProtocolEncoderAcurite_5n1 {
47+
SubGhzProtocolEncoderBase base;
48+
49+
SubGhzProtocolBlockEncoder encoder;
50+
WSBlockGeneric generic;
51+
};
52+
53+
typedef enum {
54+
Acurite_5n1DecoderStepReset = 0,
55+
Acurite_5n1DecoderStepCheckPreambule,
56+
Acurite_5n1DecoderStepSaveDuration,
57+
Acurite_5n1DecoderStepCheckDuration,
58+
} Acurite_5n1DecoderStep;
59+
60+
const SubGhzProtocolDecoder ws_protocol_acurite_5n1_decoder = {
61+
.alloc = ws_protocol_decoder_acurite_5n1_alloc,
62+
.free = ws_protocol_decoder_acurite_5n1_free,
63+
64+
.feed = ws_protocol_decoder_acurite_5n1_feed,
65+
.reset = ws_protocol_decoder_acurite_5n1_reset,
66+
67+
.get_hash_data = ws_protocol_decoder_acurite_5n1_get_hash_data,
68+
.serialize = ws_protocol_decoder_acurite_5n1_serialize,
69+
.deserialize = ws_protocol_decoder_acurite_5n1_deserialize,
70+
.get_string = ws_protocol_decoder_acurite_5n1_get_string,
71+
};
72+
73+
const SubGhzProtocolEncoder ws_protocol_acurite_5n1_encoder = {
74+
.alloc = NULL,
75+
.free = NULL,
76+
77+
.deserialize = NULL,
78+
.stop = NULL,
79+
.yield = NULL,
80+
};
81+
82+
const SubGhzProtocol ws_protocol_acurite_5n1 = {
83+
.name = WS_PROTOCOL_ACURITE_5N1_NAME,
84+
.type = SubGhzProtocolWeatherStation,
85+
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
86+
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
87+
88+
.decoder = &ws_protocol_acurite_5n1_decoder,
89+
.encoder = &ws_protocol_acurite_5n1_encoder,
90+
};
91+
92+
void* ws_protocol_decoder_acurite_5n1_alloc(SubGhzEnvironment* environment) {
93+
UNUSED(environment);
94+
WSProtocolDecoderAcurite_5n1* instance = malloc(sizeof(WSProtocolDecoderAcurite_5n1));
95+
instance->base.protocol = &ws_protocol_acurite_5n1;
96+
instance->generic.protocol_name = instance->base.protocol->name;
97+
return instance;
98+
}
99+
100+
void ws_protocol_decoder_acurite_5n1_free(void* context) {
101+
furi_assert(context);
102+
WSProtocolDecoderAcurite_5n1* instance = context;
103+
free(instance);
104+
}
105+
106+
void ws_protocol_decoder_acurite_5n1_reset(void* context) {
107+
furi_assert(context);
108+
WSProtocolDecoderAcurite_5n1* instance = context;
109+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
110+
}
111+
112+
static bool ws_protocol_acurite_5n1_check_crc(WSProtocolDecoderAcurite_5n1* instance) {
113+
uint8_t msg[] = {
114+
instance->decoder.decode_data >> 56,
115+
instance->decoder.decode_data >> 48,
116+
instance->decoder.decode_data >> 40,
117+
instance->decoder.decode_data >> 32,
118+
instance->decoder.decode_data >> 24,
119+
instance->decoder.decode_data >> 16,
120+
instance->decoder.decode_data >> 8};
121+
122+
if((subghz_protocol_blocks_add_bytes(msg, 7) ==
123+
(uint8_t)(instance->decoder.decode_data & 0xFF)) &&
124+
(!subghz_protocol_blocks_parity_bytes(&msg[2], 5))) {
125+
return true;
126+
} else {
127+
return false;
128+
}
129+
}
130+
131+
static bool ws_protocol_acurite_5n1_check_message_type(WSProtocolDecoderAcurite_5n1* instance) {
132+
if(((instance->decoder.decode_data >> 40) & 0x3F) == 0x38) {
133+
return true;
134+
} else {
135+
return false;
136+
}
137+
}
138+
139+
/**
140+
* Analysis of received data
141+
* @param instance Pointer to a WSBlockGeneric* instance
142+
*/
143+
static void ws_protocol_acurite_5n1_remote_controller(WSBlockGeneric* instance) {
144+
uint8_t channel[] = {3, 0, 2, 1};
145+
uint8_t channel_raw = ((instance->data >> 62) & 0x03);
146+
instance->channel = channel[channel_raw];
147+
instance->id = (instance->data >> 48) & 0x3FFF;
148+
instance->battery_low = !((instance->data >> 46) & 1);
149+
instance->humidity = (instance->data >> 8) & 0x7F;
150+
151+
uint16_t temp_raw = ((instance->data >> (24 - 7)) & 0x780) | ((instance->data >> 16) & 0x7F);
152+
instance->temp = locale_fahrenheit_to_celsius(((float)(temp_raw)-400) / 10.0f);
153+
154+
instance->btn = WS_NO_BTN;
155+
}
156+
157+
void ws_protocol_decoder_acurite_5n1_feed(void* context, bool level, uint32_t duration) {
158+
furi_assert(context);
159+
WSProtocolDecoderAcurite_5n1* instance = context;
160+
161+
switch(instance->decoder.parser_step) {
162+
case Acurite_5n1DecoderStepReset:
163+
if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short * 3) <
164+
ws_protocol_acurite_5n1_const.te_delta * 2)) {
165+
instance->decoder.parser_step = Acurite_5n1DecoderStepCheckPreambule;
166+
instance->decoder.te_last = duration;
167+
instance->header_count = 0;
168+
}
169+
break;
170+
171+
case Acurite_5n1DecoderStepCheckPreambule:
172+
if(level) {
173+
instance->decoder.te_last = duration;
174+
} else {
175+
if((DURATION_DIFF(
176+
instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short * 3) <
177+
ws_protocol_acurite_5n1_const.te_delta * 2) &&
178+
(DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short * 3) <
179+
ws_protocol_acurite_5n1_const.te_delta * 2)) {
180+
//Found preambule
181+
instance->header_count++;
182+
} else if((instance->header_count > 2) && (instance->header_count < 5)) {
183+
if((DURATION_DIFF(
184+
instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short) <
185+
ws_protocol_acurite_5n1_const.te_delta) &&
186+
(DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_long) <
187+
ws_protocol_acurite_5n1_const.te_delta)) {
188+
instance->decoder.decode_data = 0;
189+
instance->decoder.decode_count_bit = 0;
190+
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
191+
instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration;
192+
} else if(
193+
(DURATION_DIFF(
194+
instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_long) <
195+
ws_protocol_acurite_5n1_const.te_delta) &&
196+
(DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short) <
197+
ws_protocol_acurite_5n1_const.te_delta)) {
198+
instance->decoder.decode_data = 0;
199+
instance->decoder.decode_count_bit = 0;
200+
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
201+
instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration;
202+
} else {
203+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
204+
}
205+
} else {
206+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
207+
}
208+
}
209+
break;
210+
211+
case Acurite_5n1DecoderStepSaveDuration:
212+
if(level) {
213+
instance->decoder.te_last = duration;
214+
instance->decoder.parser_step = Acurite_5n1DecoderStepCheckDuration;
215+
} else {
216+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
217+
}
218+
break;
219+
220+
case Acurite_5n1DecoderStepCheckDuration:
221+
if(!level) {
222+
if(duration >= ((uint32_t)ws_protocol_acurite_5n1_const.te_short * 5)) {
223+
if((instance->decoder.decode_count_bit ==
224+
ws_protocol_acurite_5n1_const.min_count_bit_for_found) &&
225+
ws_protocol_acurite_5n1_check_crc(instance) &&
226+
ws_protocol_acurite_5n1_check_message_type(instance)) {
227+
instance->generic.data = instance->decoder.decode_data;
228+
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
229+
ws_protocol_acurite_5n1_remote_controller(&instance->generic);
230+
if(instance->base.callback)
231+
instance->base.callback(&instance->base, instance->base.context);
232+
}
233+
instance->decoder.decode_data = 0;
234+
instance->decoder.decode_count_bit = 0;
235+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
236+
break;
237+
} else if(
238+
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short) <
239+
ws_protocol_acurite_5n1_const.te_delta) &&
240+
(DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_long) <
241+
ws_protocol_acurite_5n1_const.te_delta)) {
242+
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
243+
instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration;
244+
} else if(
245+
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_long) <
246+
ws_protocol_acurite_5n1_const.te_delta) &&
247+
(DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short) <
248+
ws_protocol_acurite_5n1_const.te_delta)) {
249+
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
250+
instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration;
251+
} else {
252+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
253+
}
254+
} else {
255+
instance->decoder.parser_step = Acurite_5n1DecoderStepReset;
256+
}
257+
break;
258+
}
259+
}
260+
261+
uint8_t ws_protocol_decoder_acurite_5n1_get_hash_data(void* context) {
262+
furi_assert(context);
263+
WSProtocolDecoderAcurite_5n1* instance = context;
264+
return subghz_protocol_blocks_get_hash_data(
265+
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
266+
}
267+
268+
SubGhzProtocolStatus ws_protocol_decoder_acurite_5n1_serialize(
269+
void* context,
270+
FlipperFormat* flipper_format,
271+
SubGhzRadioPreset* preset) {
272+
furi_assert(context);
273+
WSProtocolDecoderAcurite_5n1* instance = context;
274+
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
275+
}
276+
277+
SubGhzProtocolStatus
278+
ws_protocol_decoder_acurite_5n1_deserialize(void* context, FlipperFormat* flipper_format) {
279+
furi_assert(context);
280+
WSProtocolDecoderAcurite_5n1* instance = context;
281+
return ws_block_generic_deserialize_check_count_bit(
282+
&instance->generic, flipper_format, ws_protocol_acurite_5n1_const.min_count_bit_for_found);
283+
}
284+
285+
void ws_protocol_decoder_acurite_5n1_get_string(void* context, FuriString* output) {
286+
furi_assert(context);
287+
WSProtocolDecoderAcurite_5n1* instance = context;
288+
furi_string_printf(
289+
output,
290+
"%s %dbit\r\n"
291+
"Key:0x%lX%08lX\r\n"
292+
"Sn:0x%lX Ch:%d Bat:%d\r\n"
293+
"Temp:%3.1f C Hum:%d%%",
294+
instance->generic.protocol_name,
295+
instance->generic.data_count_bit,
296+
(uint32_t)(instance->generic.data >> 32),
297+
(uint32_t)(instance->generic.data),
298+
instance->generic.id,
299+
instance->generic.channel,
300+
instance->generic.battery_low,
301+
(double)instance->generic.temp,
302+
instance->generic.humidity);
303+
}

0 commit comments

Comments
 (0)