Skip to content

Commit c60f574

Browse files
authored
Add support for Homelead-HG9901 soil sensor (#3299)
1 parent b302960 commit c60f574

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ See [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
365365
[277] Apator Metra E-RM 30
366366
[278] ThermoPro TX-7B Outdoor Thermometer Hygrometer
367367
[279] Nexus, CRX, Prego sauna temperature sensor
368+
[280] Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor
368369
369370
* Disabled by default, use -R n or a conf file to enable
370371

conf/rtl_433.example.conf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ convert si
518518
protocol 277 # Apator Metra E-RM 30
519519
protocol 278 # ThermoPro TX-7B Outdoor Thermometer Hygrometer
520520
protocol 279 # Nexus, CRX, Prego sauna temperature sensor
521+
protocol 280 # Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor
521522

522523
## Flex devices (command line option "-X")
523524

include/rtl_433_devices.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@
287287
DECL(apator_metra_erm30) \
288288
DECL(thermopro_tx7b) \
289289
DECL(nexus_sauna) \
290+
DECL(homelead_hg9901) \
290291

291292
/* Add new decoders here. */
292293

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ add_library(r_433 STATIC
142142
devices/hcs200.c
143143
devices/hideki.c
144144
devices/holman_ws5029.c
145+
devices/homelead_hg9901.c
145146
devices/hondaremote.c
146147
devices/honeywell.c
147148
devices/honeywell_cm921.c

src/devices/homelead_hg9901.c

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/** @file
2+
Homelead HG9901 soil moisture/temp/light level sensor decoder.
3+
4+
Copyright (C) 2025 Boing <[email protected]>, \@inonoob
5+
and Christian W. Zuckschwerdt <[email protected]>
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+
Homelead HG9901 soil moisture/temp/light level sensor decoder.
17+
18+
- Shenzhen Homelead Electronics Co., LTD. Wireless Soil Monitor HG9901, e.g. ASIN B0CRKN18C9
19+
FCC ID: 2AAXF‐HG9901, Model No: HG01, https://fccid.io/2AAXF-HG9901
20+
21+
Known rebrands:
22+
- Geevon T23033 / T230302 Soil Moisture/Temp/Light Level Sensor, ASIN B0D9Z9HLYD
23+
see #2977 by emmjaibi for excellent analysis
24+
- Dr.Meter soil sensor, ASIN B0CQKYTBC6
25+
- Royal Gardineer ZX8859-944, ASIN B0DQTYYZK8
26+
- Various other rebrands: Reyke, Vodeson, Midlocater, Kithouse, Vingnut
27+
- some unbranded sensors on AliEexpress
28+
29+
S.a. #2977 #3189 #3190 #3194 #3299
30+
31+
This device is a simple garden temperature/moisture transmitter with a small LCD display for local viewing.
32+
33+
Example codes:
34+
raw {65}55aaee8ddae84fcf
35+
inverted {65}aa5513fd001630800
36+
37+
The sensor will send a message every ~30 mins if no changes are measured.
38+
If changes are measured the sensor will instantly send messages.
39+
This might not happen if the changes have a matching checksum -- apparently that's the check used by the sensor.
40+
E.g. Moisture 62%, Temperature 23 C, Light Level: 4
41+
matches Moisture 59%, Temperature 24 C, Light Level: 6.
42+
43+
The minimum battery voltage seems to be 1.18V.
44+
45+
# Data transmission
46+
47+
9 repeats of 433.92 MHz (EU region).
48+
Modulation is OOK PWM with 400/1200 us timing, inverted bits.
49+
50+
# Data Layout
51+
52+
PPPP PPPP PPPP PPPP IIII IIII IIII IIII MMMM MMMM STTT TTTT QQBB LLLL CCCC XXXXXXXX
53+
54+
- P = Preamble of 16 bits with 0xaa55 (inverted)
55+
- I = ID 16 bits, seems to survive battery changes
56+
- M = soil moisture 0-100% as an 8 bit integer
57+
- S = sign for temperature (0 for positive or 1 for negative)
58+
- T = Temperature as 7 bit integer ~0-100C
59+
- Q = 2 sequence bits
60+
- device sends message on CHS change !
61+
- sequence:
62+
- S 00 initial phase duration 150 secs
63+
- S 01 interval timer 3 mins
64+
- S 02 interval timer 15 mins
65+
- S 03 interval timer 30 mins
66+
- B = battery status of 1 (1.22 V) to 3 (above 1.42 V), 0 so far has not been observed?
67+
- L = light level (9 states from LOW- to HIGH+)
68+
- 0 (LOW-) 0
69+
- 1 (LOW) > 120 Lux
70+
- 2 (LOW+) > 250 Lux
71+
- 3 (NOR-) > 480 Lux
72+
- 4 (NOR) > 750 Lux
73+
- 5 (NOR+) >1200 Lux
74+
- 6 (HIGH-) >1700 Lux
75+
- 7 (HIGH) >3800 Lux
76+
- 8 (HIGH+) >5200 Lux, max should be 15000 Lux
77+
- C = 4 bit checksum
78+
- X = Trailer of 8 bits equal to 0xf8 , can be ignored
79+
80+
Note: Device drifts in direct sun and shows up to 12C offset.
81+
Note: Device is NOT waterproof (IP27), don't immerse in water.
82+
Note: Uses one AA battery AA or rechargeable cell, lasts for up to: 18 months.
83+
*/
84+
static int homelead_hg9901_decoder(r_device *decoder, bitbuffer_t *bitbuffer)
85+
{
86+
uint8_t const preamble[] = {0x55, 0xaa};
87+
// Rough estimate of Lux values for light levels 0 - 8.
88+
int const lux_estimate[] = {60, 200, 400, 600, 1000, 1500, 2800, 4500, 10000, -1, -1, -1, -1, -1, -1, -1};
89+
90+
int row = bitbuffer_find_repeated_row(bitbuffer, 1, 65); // expected are 12 repeats but 1 is enough
91+
if (row < 0) {
92+
return DECODE_ABORT_EARLY; // no good row found
93+
}
94+
95+
// Check that bits_per_row is 65 or a few bits more
96+
unsigned row_len = bitbuffer->bits_per_row[row];
97+
if (row_len > 65 + 8) {
98+
return DECODE_ABORT_EARLY; // wrong Data Length (must be 65)
99+
}
100+
101+
// Search preamble
102+
unsigned pos = bitbuffer_search(bitbuffer, row, 0, preamble, 16);
103+
if (pos + 65 > row_len) {
104+
return DECODE_ABORT_LENGTH; // preamble not found or packet truncated
105+
}
106+
107+
// Invert data
108+
bitbuffer_invert(bitbuffer);
109+
110+
uint8_t *b = bitbuffer->bb[row];
111+
112+
// Nibble-wide checksum validation
113+
int chk = (b[7] & 0xf0) >> 4;
114+
int sum = add_nibbles(b, 7) & 0x0f;
115+
116+
if (sum != chk) {
117+
return DECODE_FAIL_MIC; // Checksum mismatch
118+
}
119+
120+
int id = (b[2] << 8) | b[3];
121+
int moisture = b[4];
122+
int t_sign = (b[5] & 0x80) >> 7;
123+
int temperature = b[5] & 0x7f;
124+
int sequence = (b[6] & 0xc0) >> 6;
125+
int batt_lvl = (b[6] & 0x30) >> 4;
126+
int light_lvl = (b[6] & 0x0f);
127+
int light_lux = lux_estimate[light_lvl];
128+
129+
if (t_sign) {
130+
temperature = (0 - temperature);
131+
}
132+
133+
/* clang-format off */
134+
data_t *data = data_make(
135+
"model", "Model", DATA_STRING, "Homelead-HG9901",
136+
"id", "ID", DATA_FORMAT, "%04X", DATA_INT, id,
137+
"battery_ok", "Battery", DATA_INT, batt_lvl > 1, // Level 1 means "Low"
138+
"battery_pct", "Battery level", DATA_INT, 100 * batt_lvl / 3, // Note: this might change with #3103
139+
"temperature_C", "Temperature", DATA_FORMAT, "%.0f C", DATA_DOUBLE, (double)temperature,
140+
"moisture", "Moisture", DATA_FORMAT, "%d %%", DATA_INT, moisture,
141+
"light_lvl", "Light level", DATA_INT, light_lvl,
142+
"light_lux", "Light", DATA_FORMAT, "%d lux", DATA_INT, light_lux,
143+
"sequence", "TX Sequence", DATA_INT, sequence,
144+
"mic", "Integrity", DATA_STRING, "CHECKSUM",
145+
NULL);
146+
/* clang-format on */
147+
148+
decoder_output_data(decoder, data);
149+
bitbuffer_invert(bitbuffer); // FIXME: DEBUG, remove this once account_event() is fixed
150+
return 1;
151+
}
152+
153+
static char const *const output_fields[] = {
154+
"model",
155+
"id",
156+
"battery_ok",
157+
"battery_pct",
158+
"temperature_C",
159+
"moisture",
160+
"light_lvl",
161+
"light_lux",
162+
"sequence",
163+
"mic",
164+
NULL,
165+
};
166+
167+
r_device const homelead_hg9901 = {
168+
.name = "Homelead HG9901 (Geevon, Dr.Meter, Royal Gardineer) soil moisture/temp/light level sensor",
169+
.modulation = OOK_PULSE_PWM,
170+
.short_width = 432, // gap is 1000
171+
.long_width = 1228, // gap is 230
172+
.gap_limit = 2000, // packet gap is 3700
173+
.reset_limit = 4500,
174+
.decode_fn = &homelead_hg9901_decoder,
175+
.fields = output_fields,
176+
};

0 commit comments

Comments
 (0)