Skip to content

Commit 92af366

Browse files
authored
Add support for Oil-SonicSmart (#2279)
1 parent b671f4b commit 92af366

File tree

7 files changed

+186
-43
lines changed

7 files changed

+186
-43
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
317317
[232] TFA Dostmann 14.1504.V2 Radio-controlled grill and meat thermometer
318318
[233]* CED7000 Shot Timer
319319
[234] Watchman Sonic Advanced / Plus
320+
[235] Oil Ultrasonic SMART FSK
320321
321322
* Disabled by default, use -R n or a conf file to enable
322323

conf/rtl_433.example.conf

+1
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ stop_after_successful_events false
456456
protocol 232 # TFA Dostmann 14.1504.V2 Radio-controlled grill and meat thermometer
457457
# protocol 233 # CED7000 Shot Timer
458458
protocol 234 # Watchman Sonic Advanced / Plus
459+
protocol 235 # Oil Ultrasonic SMART FSK
459460

460461
## Flex devices (command line option "-X")
461462

include/rtl_433_devices.h

+1
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@
242242
DECL(tfa_14_1504_v2) \
243243
DECL(ced7000) \
244244
DECL(oil_watchman_advanced) \
245+
DECL(oil_smart) \
245246

246247
/* Add new decoders here. */
247248

src/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ add_library(r_433 STATIC
170170
devices/nexus.c
171171
devices/nice_flor_s.c
172172
devices/norgo.c
173+
devices/oil_smart.c
173174
devices/oil_standard.c
174175
devices/oil_watchman.c
175176
devices/oil_watchman_advanced.c

src/devices/oil_smart.c

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/** @file
2+
Oil tank monitor using manchester encoded FSK protocol with CRC.
3+
4+
Copyright (C) 2022 Christian W. Zuckschwerdt <[email protected]>
5+
Device analysis by StarMonkey1
6+
7+
This program is free software; you can redistribute it and/or modify
8+
it under the terms of the GNU General Public License as published by
9+
the Free Software Foundation; either version 2 of the License, or
10+
(at your option) any later version.
11+
*/
12+
13+
#include "decoder.h"
14+
15+
/**
16+
Oil tank monitor using manchester encoded FSK protocol with CRC.
17+
18+
Tested devices:
19+
- Apollo Ultrasonic Smart liquid monitor (FSK, 433.92M) Issue #2244
20+
21+
Should apply to similar Watchman, Beckett, and Apollo devices too.
22+
23+
There is a preamble plus de-sync of 555558, then MC coded an inner preamble of 5558 (raw 9999996a).
24+
End of frame is the last half-bit repeated additional 2 times, then 4 times mark.
25+
26+
The sensor sends a single packet once every half hour or twice a second
27+
for 5 minutes when in pairing/test mode.
28+
29+
Depth reading is in cm, lowest reading is unknown, highest is unknown, invalid is unknown.
30+
31+
Data Format:
32+
33+
PRE?16h ID?16h FLAGS?16h CM:8d CRC:8h
34+
35+
Data Layout:
36+
37+
PP PP II II FF CC DD XX
38+
39+
- P: 16 bit Preamble of 0x5558
40+
- I: 16 bit Sensor ID
41+
- F: 8 bit Flags maybe
42+
- C: 8 bit Counter maybe
43+
- D: 8 bit Depth in cm, could have a MSB somewhere?
44+
- X: 8 bit CRC-8, poly 0x31 init 0x00, bit reflected
45+
46+
example packets are:
47+
48+
- raw: {158}555558 9999 996a 6559aaa99996a55696a9a5963c
49+
- aligned: {134}9999996a 6559aaa999969aa6aa9a6995 fc
50+
- decoded: 5558 bd01 5642 0497
51+
52+
TODO: this is not confirmed
53+
Start of frame full preamble is depending on first data bit either
54+
55+
0101 0101 0101 0101 0101 0111 01
56+
0101 0101 0101 0101 0101 1000 10
57+
*/
58+
static int oil_smart_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)
59+
{
60+
bitbuffer_t databits = {0};
61+
bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 64);
62+
63+
if (databits.bits_per_row[0] < 64) {
64+
return 0; // DECODE_ABORT_LENGTH; // TODO: fix calling code to handle negative return values
65+
}
66+
67+
uint8_t *b = databits.bb[0];
68+
69+
if (b[0] != 0x55 || b[1] != 0x58) {
70+
decoder_log(decoder, 2, __func__, "Couldn't find preamble");
71+
return 0; // DECODE_FAIL_SANITY; // TODO: fix calling code to handle negative return values
72+
}
73+
74+
if (crc8le(b, 8, 0x31, 0x00)) {
75+
decoder_log(decoder, 2, __func__, "CRC8 fail");
76+
return 0; // DECODE_FAIL_MIC; // TODO: fix calling code to handle negative return values
77+
}
78+
79+
// We do not know if the unit ID changes when you rebind
80+
// by holding a magnet to the sensor for long enough?
81+
uint16_t unit_id = (b[2] << 8) | b[3];
82+
83+
// TODO: none of these are identified.
84+
uint8_t unknown1 = b[4]; // idle seems to be 0x17
85+
uint8_t unknown2 = b[5]; // appears to change variously to: 0c 0e 10 12
86+
87+
// TODO: the value for a bad reading has not been found?
88+
// TODO: there could be more (MSB) bits to this?
89+
uint16_t depth = b[6];
90+
91+
/* clang-format off */
92+
data_t *data = data_make(
93+
"model", "", DATA_STRING, "Oil-Ultrasonic",
94+
"id", "", DATA_FORMAT, "%04x", DATA_INT, unit_id,
95+
"depth_cm", "", DATA_INT, depth,
96+
"unknown1", "", DATA_FORMAT, "%02x", DATA_INT, unknown1,
97+
"unknown2", "", DATA_FORMAT, "%02x", DATA_INT, unknown2,
98+
NULL);
99+
/* clang-format on */
100+
101+
decoder_output_data(decoder, data);
102+
return 1;
103+
}
104+
105+
/**
106+
Oil tank monitor using manchester encoded FSK protocol with CRC.
107+
@sa oil_smart_decode()
108+
*/
109+
static int oil_smart_callback(r_device *decoder, bitbuffer_t *bitbuffer)
110+
{
111+
uint8_t const preamble_pattern[2] = {0x55, 0x58};
112+
// End of frame is the last half-bit repeated additional 2 times, then 4 times mark.
113+
114+
unsigned bitpos = 0;
115+
int events = 0;
116+
117+
// Find a preamble with enough bits after it that it could be a complete packet
118+
while ((bitpos = bitbuffer_search(bitbuffer, 0, bitpos, preamble_pattern, 16)) + 128 <=
119+
bitbuffer->bits_per_row[0]) {
120+
events += oil_smart_decode(decoder, bitbuffer, 0, bitpos + 16);
121+
bitpos += 2;
122+
}
123+
124+
return events;
125+
}
126+
127+
static char *output_fields[] = {
128+
"model",
129+
"id",
130+
"depth_cm",
131+
"unknown1",
132+
"unknown2",
133+
NULL,
134+
};
135+
136+
r_device const oil_smart = {
137+
.name = "Oil Ultrasonic SMART FSK",
138+
.modulation = FSK_PULSE_PCM,
139+
.short_width = 500,
140+
.long_width = 500,
141+
.reset_limit = 2000,
142+
.decode_fn = &oil_smart_callback,
143+
.fields = output_fields,
144+
};

src/devices/oil_standard.c

+19-22
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
/** @file
22
Oil tank monitor using manchester encoded FSK/ASK protocol.
33
4-
Tested devices:
5-
- APOLLO ULTRASONIC STANDARD (maybe also VISUAL but not SMART, FSK)
6-
- Tekelek TEK377E (E: European, A: American version)
7-
- Beckett Rocket TEK377A (915MHz, ASK)
8-
Should apply to similar Watchman, Beckett, and Apollo devices too.
9-
104
Copyright (C) 2017 Christian W. Zuckschwerdt <[email protected]>
115
based on code Copyright (C) 2015 David Woodhouse
126
@@ -21,6 +15,13 @@
2115
/**
2216
Oil tank monitor using manchester encoded FSK/ASK protocol.
2317
18+
Tested devices:
19+
- APOLLO ULTRASONIC STANDARD (maybe also VISUAL but not SMART, FSK)
20+
- Tekelek TEK377E (E: European, A: American version)
21+
- Beckett Rocket TEK377A (915MHz, ASK)
22+
23+
Should apply to similar Watchman, Beckett, and Apollo devices too.
24+
2425
The sensor sends a single packet once every hour or twice a second
2526
for 11 minutes when in pairing/test mode (pairing needs 35 sec).
2627
depth reading is in cm, lowest reading is ~3, highest is ~305, 0 is invalid
@@ -41,25 +42,17 @@ Start of frame full preamble is depending on first data bit either
4142
*/
4243
static int oil_standard_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsigned row, unsigned bitpos)
4344
{
44-
data_t *data;
45-
uint8_t *b;
46-
uint16_t unit_id;
47-
uint16_t depth = 0;
48-
uint16_t binding_countdown = 0;
49-
uint8_t flags;
50-
uint8_t alarm;
5145
bitbuffer_t databits = {0};
52-
53-
bitpos = bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 41);
46+
bitbuffer_manchester_decode(bitbuffer, row, bitpos, &databits, 41);
5447

5548
if (databits.bits_per_row[0] < 32 || databits.bits_per_row[0] > 40 || (databits.bb[0][4] & 0xfe) != 0)
5649
return 0; // TODO: fix calling code to handle negative return values
5750

58-
b = databits.bb[0];
51+
uint8_t *b = databits.bb[0];
5952

6053
// The unit ID changes when you rebind by holding a magnet to the
6154
// sensor for long enough.
62-
unit_id = (b[0] << 8) | b[1];
55+
uint16_t unit_id = (b[0] << 8) | b[1];
6356

6457
// 0x01: Rebinding (magnet held to sensor)
6558
// 0x02: High-bit for depth
@@ -69,22 +62,26 @@ static int oil_standard_decode(r_device *decoder, bitbuffer_t *bitbuffer, unsign
6962
// 0x20: (unknown toggle)
7063
// 0x40: (unknown toggle)
7164
// 0x80: (always zero?)
72-
flags = b[2] & ~0x0A;
73-
alarm = (b[2] & 0x08) >> 3;
65+
uint8_t flags = b[2] & ~0x0A;
66+
uint8_t alarm = (b[2] & 0x08) >> 3;
7467

75-
if (flags & 1)
68+
uint16_t depth = 0;
69+
uint16_t binding_countdown = 0;
70+
if (flags & 1) {
7671
// When binding, the countdown counts up from 0x40 to 0x4a
7772
// (as long as you hold the magnet to it for long enough)
7873
// before the device ID changes. The receiver unit needs
7974
// to receive this *strongly* in order to change its
8075
// allegiance.
8176
binding_countdown = b[3];
82-
else
77+
}
78+
else {
8379
// A depth reading of zero indicates no reading.
8480
depth = ((b[2] & 0x02) << 7) | b[3];
81+
}
8582

8683
/* clang-format off */
87-
data = data_make(
84+
data_t *data = data_make(
8885
"model", "", DATA_STRING, "Oil-SonicStd",
8986
"id", "", DATA_FORMAT, "%04x", DATA_INT, unit_id,
9087
"flags", "", DATA_FORMAT, "%02x", DATA_INT, flags,

src/devices/oil_watchman.c

+19-21
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,24 @@
88
the Free Software Foundation; either version 2 of the License, or
99
(at your option) any later version.
1010
*/
11+
12+
#include "decoder.h"
13+
1114
/**
1215
Oil tank monitor using Si4320 framed FSK protocol.
1316
1417
Tested devices:
1518
- Sensor Systems Watchman Sonic
1619
*/
17-
#include "decoder.h"
18-
19-
static int oil_watchman_callback(r_device *decoder, bitbuffer_t *bitbuffer)
20+
static int oil_watchman_decode(r_device *decoder, bitbuffer_t *bitbuffer)
2021
{
2122
// Start of frame preamble is 111000xx
2223
uint8_t const preamble_pattern[] = {0xe0};
2324

2425
// End of frame is 00xxxxxx or 11xxxxxx depending on final data bit
2526
uint8_t const postamble_pattern[2] = {0x00, 0xc0};
2627

27-
uint8_t *b;
28-
uint32_t unit_id;
29-
uint16_t depth = 0;
30-
uint16_t binding_countdown = 0;
31-
uint8_t flags;
32-
uint8_t maybetemp;
33-
double temperature;
34-
data_t *data;
3528
unsigned bitpos = 0;
36-
bitbuffer_t databits = {0};
3729
int events = 0;
3830

3931
// Find a preamble with enough bits after it that it could be a complete packet
@@ -43,11 +35,12 @@ static int oil_watchman_callback(r_device *decoder, bitbuffer_t *bitbuffer)
4335
// Skip the matched preamble bits to point to the data
4436
bitpos += 6;
4537

38+
bitbuffer_t databits = {0};
4639
bitpos = bitbuffer_manchester_decode(bitbuffer, 0, bitpos, &databits, 64);
4740
if (databits.bits_per_row[0] != 64)
4841
continue; // DECODE_ABORT_LENGTH
4942

50-
b = databits.bb[0];
43+
uint8_t *b = databits.bb[0];
5144

5245
// Check for postamble, depending on last data bit
5346
if (bitbuffer_search(bitbuffer, 0, bitpos, &postamble_pattern[b[7] & 1], 2) != bitpos)
@@ -58,31 +51,36 @@ static int oil_watchman_callback(r_device *decoder, bitbuffer_t *bitbuffer)
5851

5952
// The unit ID changes when you rebind by holding a magnet to the
6053
// sensor for long enough; it seems to be time-based.
61-
unit_id = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
54+
uint32_t unit_id = ((unsigned)b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
6255

6356
// 0x01: Rebinding (magnet held to sensor)
6457
// 0x08: Leak/theft alarm
6558
// top three bits seem also to vary with temperature (independently of maybetemp)
66-
flags = b[4];
59+
uint8_t flags = b[4];
6760

6861
// Not entirely sure what this is but it might be inversely
6962
// proportional to temperature.
70-
maybetemp = b[5] >> 2;
71-
temperature = (double)(145.0 - 5.0 * maybetemp) / 3.0;
72-
if (flags & 1)
63+
uint8_t maybetemp = b[5] >> 2;
64+
double temperature = (double)(145.0 - 5.0 * maybetemp) / 3.0;
65+
66+
uint16_t depth = 0;
67+
uint16_t binding_countdown = 0;
68+
if (flags & 1) {
7369
// When binding, the countdown counts up from 0x51 to 0x5a
7470
// (as long as you hold the magnet to it for long enough)
7571
// before the device ID changes. The receiver unit needs
7672
// to receive this *strongly* in order to change its
7773
// allegiance.
7874
binding_countdown = b[6];
79-
else
75+
}
76+
else {
8077
// A depth reading of zero indicates no reading. Even with
8178
// the sensor flat down on a table, it still reads about 13.
8279
depth = ((b[5] & 3) << 8) | b[6];
80+
}
8381

8482
/* clang-format off */
85-
data = data_make(
83+
data_t *data = data_make(
8684
"model", "", DATA_STRING, "Oil-SonicSmart",
8785
"id", "", DATA_FORMAT, "%06x", DATA_INT, unit_id,
8886
"flags", "", DATA_FORMAT, "%02x", DATA_INT, flags,
@@ -116,6 +114,6 @@ r_device const oil_watchman = {
116114
.short_width = 1000,
117115
.long_width = 1000, // NRZ
118116
.reset_limit = 4000,
119-
.decode_fn = &oil_watchman_callback,
117+
.decode_fn = &oil_watchman_decode,
120118
.fields = output_fields,
121119
};

0 commit comments

Comments
 (0)