Skip to content

Commit 49d842e

Browse files
clashlabhedgerskotopes
authored
weather_station: add oregon3 with THGR221 (#2748)
Co-authored-by: hedger <[email protected]> Co-authored-by: あく <[email protected]>
1 parent 0e4344a commit 49d842e

File tree

4 files changed

+374
-1
lines changed

4 files changed

+374
-1
lines changed
Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
#include "oregon3.h"
2+
3+
#include <lib/subghz/blocks/const.h>
4+
#include <lib/subghz/blocks/decoder.h>
5+
#include <lib/subghz/blocks/encoder.h>
6+
#include <lib/subghz/blocks/math.h>
7+
#include "ws_generic.h"
8+
9+
#include <lib/toolbox/manchester_decoder.h>
10+
#include <lib/flipper_format/flipper_format_i.h>
11+
12+
#define TAG "WSProtocolOregon3"
13+
14+
static const SubGhzBlockConst ws_oregon3_const = {
15+
.te_long = 1100,
16+
.te_short = 500,
17+
.te_delta = 300,
18+
.min_count_bit_for_found = 32,
19+
};
20+
21+
#define OREGON3_PREAMBLE_BITS 28
22+
#define OREGON3_PREAMBLE_MASK 0b1111111111111111111111111111
23+
// 24 ones + 0101 (inverted A)
24+
#define OREGON3_PREAMBLE 0b1111111111111111111111110101
25+
26+
// Fixed part contains:
27+
// - Sensor type: 16 bits
28+
// - Channel: 4 bits
29+
// - ID (changes when batteries are changed): 8 bits
30+
// - Battery status: 4 bits
31+
#define OREGON3_FIXED_PART_BITS (16 + 4 + 8 + 4)
32+
#define OREGON3_SENSOR_ID(d) (((d) >> 16) & 0xFFFF)
33+
#define OREGON3_CHECKSUM_BITS 8
34+
35+
// bit indicating the low battery
36+
#define OREGON3_FLAG_BAT_LOW 0x4
37+
38+
/// Documentation for Oregon Scientific protocols can be found here:
39+
/// https://www.osengr.org/Articles/OS-RF-Protocols-IV.pdf
40+
// Sensors ID
41+
#define ID_THGR221 0xf824
42+
43+
struct WSProtocolDecoderOregon3 {
44+
SubGhzProtocolDecoderBase base;
45+
46+
SubGhzBlockDecoder decoder;
47+
WSBlockGeneric generic;
48+
ManchesterState manchester_state;
49+
bool prev_bit;
50+
51+
uint8_t var_bits;
52+
uint64_t var_data;
53+
};
54+
55+
typedef struct WSProtocolDecoderOregon3 WSProtocolDecoderOregon3;
56+
57+
typedef enum {
58+
Oregon3DecoderStepReset = 0,
59+
Oregon3DecoderStepFoundPreamble,
60+
Oregon3DecoderStepVarData,
61+
} Oregon3DecoderStep;
62+
63+
void* ws_protocol_decoder_oregon3_alloc(SubGhzEnvironment* environment) {
64+
UNUSED(environment);
65+
WSProtocolDecoderOregon3* instance = malloc(sizeof(WSProtocolDecoderOregon3));
66+
instance->base.protocol = &ws_protocol_oregon3;
67+
instance->generic.protocol_name = instance->base.protocol->name;
68+
instance->generic.humidity = WS_NO_HUMIDITY;
69+
instance->generic.temp = WS_NO_TEMPERATURE;
70+
instance->generic.btn = WS_NO_BTN;
71+
instance->generic.channel = WS_NO_CHANNEL;
72+
instance->generic.battery_low = WS_NO_BATT;
73+
instance->generic.id = WS_NO_ID;
74+
instance->prev_bit = false;
75+
return instance;
76+
}
77+
78+
void ws_protocol_decoder_oregon3_free(void* context) {
79+
furi_assert(context);
80+
WSProtocolDecoderOregon3* instance = context;
81+
free(instance);
82+
}
83+
84+
void ws_protocol_decoder_oregon3_reset(void* context) {
85+
furi_assert(context);
86+
WSProtocolDecoderOregon3* instance = context;
87+
instance->decoder.parser_step = Oregon3DecoderStepReset;
88+
instance->decoder.decode_data = 0UL;
89+
instance->decoder.decode_count_bit = 0;
90+
manchester_advance(
91+
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
92+
instance->prev_bit = false;
93+
instance->var_data = 0;
94+
instance->var_bits = 0;
95+
}
96+
97+
static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration) {
98+
bool is_long = false;
99+
100+
if(DURATION_DIFF(duration, ws_oregon3_const.te_long) < ws_oregon3_const.te_delta) {
101+
is_long = true;
102+
} else if(DURATION_DIFF(duration, ws_oregon3_const.te_short) < ws_oregon3_const.te_delta) {
103+
is_long = false;
104+
} else {
105+
return ManchesterEventReset;
106+
}
107+
108+
if(level)
109+
return is_long ? ManchesterEventLongHigh : ManchesterEventShortHigh;
110+
else
111+
return is_long ? ManchesterEventLongLow : ManchesterEventShortLow;
112+
}
113+
114+
// From sensor id code return amount of bits in variable section
115+
// https://temofeev.ru/info/articles/o-dekodirovanii-protokola-pogodnykh-datchikov-oregon-scientific
116+
static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) {
117+
switch(sensor_id) {
118+
case ID_THGR221:
119+
default:
120+
// nibbles: temp + hum + '0'
121+
return (4 + 2 + 1) * 4;
122+
}
123+
}
124+
125+
static void ws_oregon3_decode_const_data(WSBlockGeneric* ws_block) {
126+
ws_block->id = OREGON3_SENSOR_ID(ws_block->data);
127+
ws_block->channel = (ws_block->data >> 12) & 0xF;
128+
ws_block->battery_low = (ws_block->data & OREGON3_FLAG_BAT_LOW) ? 1 : 0;
129+
}
130+
131+
static uint16_t ws_oregon3_bcd_decode_short(uint32_t data) {
132+
return (data & 0xF) * 10 + ((data >> 4) & 0xF);
133+
}
134+
135+
static float ws_oregon3_decode_temp(uint32_t data) {
136+
int32_t temp_val;
137+
temp_val = ws_oregon3_bcd_decode_short(data >> 4);
138+
temp_val *= 10;
139+
temp_val += (data >> 12) & 0xF;
140+
if(data & 0xF) temp_val = -temp_val;
141+
return (float)temp_val / 10.0;
142+
}
143+
144+
static void ws_oregon3_decode_var_data(WSBlockGeneric* ws_b, uint16_t sensor_id, uint32_t data) {
145+
switch(sensor_id) {
146+
case ID_THGR221:
147+
default:
148+
ws_b->humidity = ws_oregon3_bcd_decode_short(data >> 4);
149+
ws_b->temp = ws_oregon3_decode_temp(data >> 12);
150+
break;
151+
}
152+
}
153+
154+
void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t duration) {
155+
furi_assert(context);
156+
WSProtocolDecoderOregon3* instance = context;
157+
// Oregon v3.0 protocol is inverted
158+
ManchesterEvent event = level_and_duration_to_event(!level, duration);
159+
160+
// low-level bit sequence decoding
161+
if(event == ManchesterEventReset) {
162+
instance->decoder.parser_step = Oregon3DecoderStepReset;
163+
instance->prev_bit = false;
164+
instance->decoder.decode_data = 0UL;
165+
instance->decoder.decode_count_bit = 0;
166+
}
167+
if(manchester_advance(
168+
instance->manchester_state, event, &instance->manchester_state, &instance->prev_bit)) {
169+
subghz_protocol_blocks_add_bit(&instance->decoder, instance->prev_bit);
170+
}
171+
172+
switch(instance->decoder.parser_step) {
173+
case Oregon3DecoderStepReset:
174+
// waiting for fixed oregon3 preamble
175+
if(instance->decoder.decode_count_bit >= OREGON3_PREAMBLE_BITS &&
176+
((instance->decoder.decode_data & OREGON3_PREAMBLE_MASK) == OREGON3_PREAMBLE)) {
177+
instance->decoder.parser_step = Oregon3DecoderStepFoundPreamble;
178+
instance->decoder.decode_count_bit = 0;
179+
instance->decoder.decode_data = 0UL;
180+
}
181+
break;
182+
case Oregon3DecoderStepFoundPreamble:
183+
// waiting for fixed oregon3 data
184+
if(instance->decoder.decode_count_bit == OREGON3_FIXED_PART_BITS) {
185+
instance->generic.data = instance->decoder.decode_data;
186+
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
187+
instance->decoder.decode_data = 0UL;
188+
instance->decoder.decode_count_bit = 0;
189+
190+
// reverse nibbles in decoded data as oregon v3.0 is LSB first
191+
instance->generic.data = (instance->generic.data & 0x55555555) << 1 |
192+
(instance->generic.data & 0xAAAAAAAA) >> 1;
193+
instance->generic.data = (instance->generic.data & 0x33333333) << 2 |
194+
(instance->generic.data & 0xCCCCCCCC) >> 2;
195+
196+
ws_oregon3_decode_const_data(&instance->generic);
197+
instance->var_bits =
198+
oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data));
199+
200+
if(!instance->var_bits) {
201+
// sensor is not supported, stop decoding, but showing the decoded fixed part
202+
instance->decoder.parser_step = Oregon3DecoderStepReset;
203+
if(instance->base.callback)
204+
instance->base.callback(&instance->base, instance->base.context);
205+
} else {
206+
instance->decoder.parser_step = Oregon3DecoderStepVarData;
207+
}
208+
}
209+
break;
210+
case Oregon3DecoderStepVarData:
211+
// waiting for variable (sensor-specific data)
212+
if(instance->decoder.decode_count_bit == instance->var_bits + OREGON3_CHECKSUM_BITS) {
213+
instance->var_data = instance->decoder.decode_data & 0xFFFFFFFFFFFFFFFF;
214+
215+
// reverse nibbles in var data
216+
instance->var_data = (instance->var_data & 0x5555555555555555) << 1 |
217+
(instance->var_data & 0xAAAAAAAAAAAAAAAA) >> 1;
218+
instance->var_data = (instance->var_data & 0x3333333333333333) << 2 |
219+
(instance->var_data & 0xCCCCCCCCCCCCCCCC) >> 2;
220+
221+
ws_oregon3_decode_var_data(
222+
&instance->generic,
223+
OREGON3_SENSOR_ID(instance->generic.data),
224+
instance->var_data >> OREGON3_CHECKSUM_BITS);
225+
226+
instance->decoder.parser_step = Oregon3DecoderStepReset;
227+
if(instance->base.callback)
228+
instance->base.callback(&instance->base, instance->base.context);
229+
}
230+
break;
231+
}
232+
}
233+
234+
uint8_t ws_protocol_decoder_oregon3_get_hash_data(void* context) {
235+
furi_assert(context);
236+
WSProtocolDecoderOregon3* instance = context;
237+
return subghz_protocol_blocks_get_hash_data(
238+
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
239+
}
240+
241+
SubGhzProtocolStatus ws_protocol_decoder_oregon3_serialize(
242+
void* context,
243+
FlipperFormat* flipper_format,
244+
SubGhzRadioPreset* preset) {
245+
furi_assert(context);
246+
WSProtocolDecoderOregon3* instance = context;
247+
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
248+
ret = ws_block_generic_serialize(&instance->generic, flipper_format, preset);
249+
if(ret != SubGhzProtocolStatusOk) return ret;
250+
uint32_t temp = instance->var_bits;
251+
if(!flipper_format_write_uint32(flipper_format, "VarBits", &temp, 1)) {
252+
FURI_LOG_E(TAG, "Error adding VarBits");
253+
return SubGhzProtocolStatusErrorParserOthers;
254+
}
255+
if(!flipper_format_write_hex(
256+
flipper_format,
257+
"VarData",
258+
(const uint8_t*)&instance->var_data,
259+
sizeof(instance->var_data))) {
260+
FURI_LOG_E(TAG, "Error adding VarData");
261+
return SubGhzProtocolStatusErrorParserOthers;
262+
}
263+
return ret;
264+
}
265+
266+
SubGhzProtocolStatus
267+
ws_protocol_decoder_oregon3_deserialize(void* context, FlipperFormat* flipper_format) {
268+
furi_assert(context);
269+
WSProtocolDecoderOregon3* instance = context;
270+
uint32_t temp_data;
271+
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
272+
do {
273+
ret = ws_block_generic_deserialize(&instance->generic, flipper_format);
274+
if(ret != SubGhzProtocolStatusOk) {
275+
break;
276+
}
277+
if(!flipper_format_read_uint32(flipper_format, "VarBits", &temp_data, 1)) {
278+
FURI_LOG_E(TAG, "Missing VarLen");
279+
ret = SubGhzProtocolStatusErrorParserOthers;
280+
break;
281+
}
282+
instance->var_bits = (uint8_t)temp_data;
283+
if(!flipper_format_read_hex(
284+
flipper_format,
285+
"VarData",
286+
(uint8_t*)&instance->var_data,
287+
sizeof(instance->var_data))) { //-V1051
288+
FURI_LOG_E(TAG, "Missing VarData");
289+
ret = SubGhzProtocolStatusErrorParserOthers;
290+
break;
291+
}
292+
if(instance->generic.data_count_bit != ws_oregon3_const.min_count_bit_for_found) {
293+
FURI_LOG_E(TAG, "Wrong number of bits in key: %d", instance->generic.data_count_bit);
294+
ret = SubGhzProtocolStatusErrorValueBitCount;
295+
break;
296+
}
297+
} while(false);
298+
return ret;
299+
}
300+
301+
static void oregon3_append_check_sum(uint32_t fix_data, uint64_t var_data, FuriString* output) {
302+
uint8_t sum = fix_data & 0xF;
303+
uint8_t ref_sum = var_data & 0xFF;
304+
var_data >>= 4;
305+
306+
for(uint8_t i = 1; i < 8; i++) {
307+
fix_data >>= 4;
308+
var_data >>= 4;
309+
sum += (fix_data & 0xF) + (var_data & 0xF);
310+
}
311+
312+
// swap calculated sum nibbles
313+
sum = (((sum >> 4) & 0xF) | (sum << 4)) & 0xFF;
314+
if(sum == ref_sum)
315+
furi_string_cat_printf(output, "Sum ok: 0x%hhX", ref_sum);
316+
else
317+
furi_string_cat_printf(output, "Sum err: 0x%hhX vs 0x%hhX", ref_sum, sum);
318+
}
319+
320+
void ws_protocol_decoder_oregon3_get_string(void* context, FuriString* output) {
321+
furi_assert(context);
322+
WSProtocolDecoderOregon3* instance = context;
323+
furi_string_cat_printf(
324+
output,
325+
"%s\r\n"
326+
"ID: 0x%04lX, ch: %d, bat: %d, rc: 0x%02lX\r\n",
327+
instance->generic.protocol_name,
328+
instance->generic.id,
329+
instance->generic.channel,
330+
instance->generic.battery_low,
331+
(uint32_t)(instance->generic.data >> 4) & 0xFF);
332+
333+
if(instance->var_bits > 0) {
334+
furi_string_cat_printf(
335+
output,
336+
"Temp:%d.%d C Hum:%d%%",
337+
(int16_t)instance->generic.temp,
338+
abs(
339+
((int16_t)(instance->generic.temp * 10) -
340+
(((int16_t)instance->generic.temp) * 10))),
341+
instance->generic.humidity);
342+
oregon3_append_check_sum((uint32_t)instance->generic.data, instance->var_data, output);
343+
}
344+
}
345+
346+
const SubGhzProtocolDecoder ws_protocol_oregon3_decoder = {
347+
.alloc = ws_protocol_decoder_oregon3_alloc,
348+
.free = ws_protocol_decoder_oregon3_free,
349+
350+
.feed = ws_protocol_decoder_oregon3_feed,
351+
.reset = ws_protocol_decoder_oregon3_reset,
352+
353+
.get_hash_data = ws_protocol_decoder_oregon3_get_hash_data,
354+
.serialize = ws_protocol_decoder_oregon3_serialize,
355+
.deserialize = ws_protocol_decoder_oregon3_deserialize,
356+
.get_string = ws_protocol_decoder_oregon3_get_string,
357+
};
358+
359+
const SubGhzProtocol ws_protocol_oregon3 = {
360+
.name = WS_PROTOCOL_OREGON3_NAME,
361+
.type = SubGhzProtocolWeatherStation,
362+
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
363+
364+
.decoder = &ws_protocol_oregon3_decoder,
365+
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#pragma once
2+
3+
#include <lib/subghz/protocols/base.h>
4+
5+
#define WS_PROTOCOL_OREGON3_NAME "Oregon3"
6+
extern const SubGhzProtocol ws_protocol_oregon3;

applications/external/weather_station/protocols/protocol_items.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
1111
&ws_protocol_lacrosse_tx,
1212
&ws_protocol_lacrosse_tx141thbv2,
1313
&ws_protocol_oregon2,
14+
&ws_protocol_oregon3,
1415
&ws_protocol_acurite_592txr,
1516
&ws_protocol_ambient_weather,
1617
&ws_protocol_auriol_th,
@@ -21,4 +22,4 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
2122

2223
const SubGhzProtocolRegistry weather_station_protocol_registry = {
2324
.items = weather_station_protocol_registry_items,
24-
.size = COUNT_OF(weather_station_protocol_registry_items)};
25+
.size = COUNT_OF(weather_station_protocol_registry_items)};

applications/external/weather_station/protocols/protocol_items.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "lacrosse_tx.h"
1212
#include "lacrosse_tx141thbv2.h"
1313
#include "oregon2.h"
14+
#include "oregon3.h"
1415
#include "acurite_592txr.h"
1516
#include "ambient_weather.h"
1617
#include "auriol_hg0601a.h"

0 commit comments

Comments
 (0)