ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
hydreon_rgxx.cpp
Go to the documentation of this file.
1#include "hydreon_rgxx.h"
2#include "esphome/core/log.h"
3#include <cstring>
4
6
7ESPHOME_LOG_TAG(TAG, "hydreon_rgxx.sensor");
8
9static constexpr uint32_t REBOOT_INTERVAL_ID = 0;
10
11#define HYDREON_RGXX_COMMA ,
12static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
13static const char *const IGNORE_STRINGS[] = {HYDREON_RGXX_IGNORE_LIST(, HYDREON_RGXX_COMMA)};
14
16 ESP_LOGCONFIG(TAG, "hydreon_rgxx:");
17 if (model_ == RG9) {
18 ESP_LOGCONFIG(TAG,
19 " Model: RG9\n"
20 " Disable Led: %s",
21 TRUEFALSE(this->disable_led_));
22 } else {
23 ESP_LOGCONFIG(TAG, " Model: RG15");
24 if (this->resolution_ == FORCE_HIGH) {
25 ESP_LOGCONFIG(TAG, " Resolution: high");
26 } else {
27 ESP_LOGCONFIG(TAG, " Resolution: low");
28 }
29 }
30 LOG_UPDATE_INTERVAL(this);
31
32 int i = 0;
33#define HYDREON_RGXX_LOG_SENSOR(s) \
34 if (this->sensors_[i++] != nullptr) { \
35 LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
36 }
37 HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
38}
39
44
46 if (this->sensors_received_ == -1) {
47 return -1;
48 }
49 int ret = NUM_SENSORS;
50 for (int i = 0; i < NUM_SENSORS; i++) {
51 if (this->sensors_[i] == nullptr) {
52 ret -= 1;
53 continue;
54 }
55 if ((this->sensors_received_ >> i & 1) != 0) {
56 ret -= 1;
57 }
58 }
59 return ret;
60}
61
63 if (this->boot_count_ > 0) {
64 if (this->num_sensors_missing_() > 0) {
65 for (int i = 0; i < NUM_SENSORS; i++) {
66 if (this->sensors_[i] == nullptr) {
67 continue;
68 }
69 if ((this->sensors_received_ >> i & 1) == 0) {
70 ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
71 }
72 }
73
74 this->no_response_count_++;
75 ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_);
76 if (this->no_response_count_ > 15) {
77 ESP_LOGE(TAG, "asking sensor to reboot");
78 for (auto &sensor : this->sensors_) {
79 if (sensor != nullptr) {
80 sensor->publish_state(NAN);
81 }
82 }
83 this->schedule_reboot_();
84 return;
85 }
86 } else {
87 this->no_response_count_ = 0;
88 }
89 this->write_str("R\n");
90#ifdef USE_BINARY_SENSOR
91 if (this->too_cold_sensor_ != nullptr) {
93 }
94 if (this->lens_bad_sensor_ != nullptr) {
96 }
97 if (this->em_sat_sensor_ != nullptr) {
99 }
100#endif
101 this->too_cold_ = false;
102 this->lens_bad_ = false;
103 this->em_sat_ = false;
104 this->sensors_received_ = 0;
105 }
106}
107
109 this->read_available([this](uint8_t data) {
110 size_t len;
111 const char *line = this->line_reader_.feed(data, len);
113 if (line != nullptr) {
114 this->process_line_(StringRef(line, len));
115 }
116 });
117}
118
134 this->boot_count_ = 0;
135 this->set_interval(REBOOT_INTERVAL_ID, 5000, [this]() {
136 if (this->boot_count_ < 0) {
137 ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
138 }
139 this->boot_count_--;
140 this->write_str("K\n");
141 if (this->boot_count_ < -5) {
142 ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
143 for (auto &sensor : this->sensors_) {
144 if (sensor != nullptr) {
145 sensor->publish_state(NAN);
146 }
147 }
148 this->mark_failed(LOG_STR("Sensor did not boot"));
149 }
150 });
151}
152
154 ESP_LOGV(TAG, "Read from serial: %s", line.c_str());
155
156 if (line[0] == ';') {
157 ESP_LOGI(TAG, "Comment: %s", line.c_str());
158 return;
159 }
160 if (line.empty()) {
161 ESP_LOGD(TAG, "Received empty line");
162 return;
163 }
164 if (line.size() == 1) {
165 // single character lines, such as acknowledgements
166 ESP_LOGD(TAG, "Received ack: %s", line.c_str());
167 return;
168 }
169 if (line.find("LensBad") != std::string::npos) {
170 ESP_LOGW(TAG, "Received LensBad!");
171 this->lens_bad_ = true;
172 }
173 if (line.find("EmSat") != std::string::npos) {
174 ESP_LOGW(TAG, "Received EmSat!");
175 this->em_sat_ = true;
176 }
177 if (line.starts_with("PwrDays")) {
178 if (this->boot_count_ <= 0) {
179 this->boot_count_ = 1;
180 } else {
181 this->boot_count_++;
182 }
183 this->cancel_interval(REBOOT_INTERVAL_ID);
184 this->no_response_count_ = 0;
185 ESP_LOGI(TAG, "Boot detected: %s", line.c_str());
186
187 if (this->model_ == RG15) {
188 if (this->resolution_ == FORCE_HIGH) {
189 this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
190 } else {
191 this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode
192 }
193 }
194
195 if (this->model_ == RG9) {
196 this->write_str("P\n"); // set sensor to (P)polling mode
197
198 if (this->disable_led_) {
199 this->write_str("D 1\n"); // set sensor (D 1)rain detection LED disabled
200 } else {
201 this->write_str("D 0\n"); // set sensor (D 0)rain detection LED enabled
202 }
203 }
204 return;
205 }
206 if (line.starts_with("SW")) {
207 StringRef::size_type majend = line.find('.');
208 StringRef::size_type endversion = line.find(' ', 3);
209 if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
210 ESP_LOGW(TAG, "invalid version string: %s", line.c_str());
211 }
212 // strtol stops at the '.' and the ' '
213 int major = line.size() > 3 ? strtol(line.c_str() + 3, nullptr, 10) : 0;
214 int minor = strtol(line.c_str() + (majend == std::string::npos ? 0 : majend + 1), nullptr, 10);
215
216 if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
217 ESP_LOGW(TAG, "invalid version: %s", line.c_str());
218 }
219 this->sw_version_ = major * 1000 + minor;
220 ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
221 return;
222 }
223 bool is_data_line = false;
224 for (int i = 0; i < NUM_SENSORS; i++) {
225 if (this->sensors_[i] == nullptr) {
226 continue;
227 }
228 StringRef::size_type n = line.find(PROTOCOL_NAMES[i]);
229 if (n == std::string::npos) {
230 continue;
231 }
232 is_data_line = true;
233
234 if (n == line.find('t', n)) {
235 // The device temperature ('t') response contains both °C and °F values:
236 // "t 72F 22C".
237 // ESPHome uses only °C, only parse °C value (move past 'F').
238 n = line.find('F', n);
239 if (n == std::string::npos) {
240 continue;
241 }
242 n += 1; // move past 'F'
243 } else {
244 n += strlen(PROTOCOL_NAMES[i]); // move past protocol name
245 }
246
247 // parse value, starting at str position n
248 float data = strtof(line.c_str() + n, nullptr);
249 this->sensors_[i]->publish_state(data);
250 ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
251 this->sensors_received_ |= (1 << i);
252 }
253 if (is_data_line) {
254 StringRef::size_type tc = line.find("TooCold");
255 this->too_cold_ |= tc != std::string::npos;
256 if (this->too_cold_) {
257 ESP_LOGD(TAG, "Received TooCold");
258 }
259 if (this->request_temperature_ && this->num_sensors_missing_() == 1) {
260 this->write_str("T\n");
261 }
262 } else {
263 for (const auto *ignore : IGNORE_STRINGS) {
264 if (line.starts_with(ignore)) {
265 ESP_LOGI(TAG, "Ignoring %s", line.c_str());
266 return;
267 }
268 }
269 ESP_LOGI(TAG, "Got unknown line: %s", line.c_str());
270 }
271}
272
273} // namespace esphome::hydreon_rgxx
void mark_failed()
Mark this component as failed.
bool cancel_interval(const char *name)
Cancel an interval function.
Definition component.cpp:92
void set_interval(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a const char* name.
Definition component.cpp:88
char * feed(uint8_t c)
Feed one byte; returns the completed line (empty lines included), valid until the next call,...
Definition line_reader.h:23
void log_if_overflowed(const char *tag) const
Log the shared "line dropped" warning when the last feed() overflowed.
Definition line_reader.h:69
StringRef is a reference to a string owned by something else.
Definition string_ref.h:31
constexpr const char * c_str() const
Definition string_ref.h:78
size_type find(const char *s, size_type pos=0) const
Find first occurrence of substring, returns std::string::npos if not found.
Definition string_ref.h:122
bool starts_with(const StringRef &prefix) const
True if the view begins with the given prefix (std::string::starts_with-like)
Definition string_ref.h:85
constexpr bool empty() const
Definition string_ref.h:81
constexpr size_type size() const
Definition string_ref.h:79
std::allocator_traits< allocator_type >::size_type size_type
Definition string_ref.h:36
void publish_state(bool new_state)
Publish a new state to the front-end.
void update() override
Schedule data readings.
void setup() override
Setup the sensor and test for a connection.
binary_sensor::BinarySensor * em_sat_sensor_
binary_sensor::BinarySensor * lens_bad_sensor_
void process_line_(StringRef line)
line is NUL terminated
void schedule_reboot_()
Communication with the sensor is asynchronous.
binary_sensor::BinarySensor * too_cold_sensor_
void loop() override
Read data once available.
sensor::Sensor * sensors_[NUM_SENSORS]
void publish_state(float state)
Publish a new state to the front-end.
Definition sensor.cpp:65
void discard_available()
Read and drop every byte waiting in the receive buffer.
Definition uart.cpp:36
void write_str(const char *str)
Definition uart.h:37
bool read_available(F &&on_byte)
Same as read_available_chunks(), calling on_byte(uint8_t) once per byte.
Definition uart.h:77
int ret
ESPHOME_LOG_TAG(TAG, "hydreon_rgxx.sensor")
const void size_t len
Definition hal.h:68
static void uint32_t