ESPHome 2026.5.0b1
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
5
6static const char *const TAG = "hydreon_rgxx.sensor";
7static const int MAX_DATA_LENGTH_BYTES = 80;
8static const uint8_t ASCII_LF = 0x0A;
9#define HYDREON_RGXX_COMMA ,
10static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
11static const char *const IGNORE_STRINGS[] = {HYDREON_RGXX_IGNORE_LIST(, HYDREON_RGXX_COMMA)};
12
15 ESP_LOGCONFIG(TAG, "hydreon_rgxx:");
16 if (this->is_failed()) {
17 ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
18 }
19 if (model_ == RG9) {
20 ESP_LOGCONFIG(TAG,
21 " Model: RG9\n"
22 " Disable Led: %s",
23 TRUEFALSE(this->disable_led_));
24 } else {
25 ESP_LOGCONFIG(TAG, " Model: RG15");
26 if (this->resolution_ == FORCE_HIGH) {
27 ESP_LOGCONFIG(TAG, " Resolution: high");
28 } else {
29 ESP_LOGCONFIG(TAG, " Resolution: low");
30 }
31 }
32 LOG_UPDATE_INTERVAL(this);
33
34 int i = 0;
35#define HYDREON_RGXX_LOG_SENSOR(s) \
36 if (this->sensors_[i++] != nullptr) { \
37 LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
38 }
39 HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
40}
41
43 while (this->available() != 0) {
44 this->read();
45 }
46 this->schedule_reboot_();
47}
48
50 if (this->sensors_received_ == -1) {
51 return -1;
52 }
53 int ret = NUM_SENSORS;
54 for (int i = 0; i < NUM_SENSORS; i++) {
55 if (this->sensors_[i] == nullptr) {
56 ret -= 1;
57 continue;
58 }
59 if ((this->sensors_received_ >> i & 1) != 0) {
60 ret -= 1;
61 }
62 }
63 return ret;
64}
65
67 if (this->boot_count_ > 0) {
68 if (this->num_sensors_missing_() > 0) {
69 for (int i = 0; i < NUM_SENSORS; i++) {
70 if (this->sensors_[i] == nullptr) {
71 continue;
72 }
73 if ((this->sensors_received_ >> i & 1) == 0) {
74 ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
75 }
76 }
77
78 this->no_response_count_++;
79 ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_);
80 if (this->no_response_count_ > 15) {
81 ESP_LOGE(TAG, "asking sensor to reboot");
82 for (auto &sensor : this->sensors_) {
83 if (sensor != nullptr) {
84 sensor->publish_state(NAN);
85 }
86 }
87 this->schedule_reboot_();
88 return;
89 }
90 } else {
91 this->no_response_count_ = 0;
92 }
93 this->write_str("R\n");
94#ifdef USE_BINARY_SENSOR
95 if (this->too_cold_sensor_ != nullptr) {
97 }
98 if (this->lens_bad_sensor_ != nullptr) {
100 }
101 if (this->em_sat_sensor_ != nullptr) {
103 }
104#endif
105 this->too_cold_ = false;
106 this->lens_bad_ = false;
107 this->em_sat_ = false;
108 this->sensors_received_ = 0;
109 }
110}
111
113 uint8_t data;
114 while (this->available() > 0) {
115 if (this->read_byte(&data)) {
116 buffer_ += (char) data;
117 if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) {
118 // complete line received
119 this->process_line_();
120 this->buffer_.clear();
121 }
122 }
123 }
124}
125
141 this->boot_count_ = 0;
142 this->set_interval("reboot", 5000, [this]() {
143 if (this->boot_count_ < 0) {
144 ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
145 }
146 this->boot_count_--;
147 this->write_str("K\n");
148 if (this->boot_count_ < -5) {
149 ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
150 for (auto &sensor : this->sensors_) {
151 if (sensor != nullptr) {
152 sensor->publish_state(NAN);
153 }
154 }
155 this->mark_failed();
156 }
157 });
158}
159
161 ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
162
163 if (buffer_[0] == ';') {
164 ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
165 return;
166 }
167 std::string::size_type newlineposn = this->buffer_.find('\n');
168 if (newlineposn <= 1) {
169 // allow both \r\n and \n
170 ESP_LOGD(TAG, "Received empty line");
171 return;
172 }
173 if (newlineposn <= 2) {
174 // single character lines, such as acknowledgements
175 ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
176 return;
177 }
178 if (this->buffer_.find("LensBad") != std::string::npos) {
179 ESP_LOGW(TAG, "Received LensBad!");
180 this->lens_bad_ = true;
181 }
182 if (this->buffer_.find("EmSat") != std::string::npos) {
183 ESP_LOGW(TAG, "Received EmSat!");
184 this->em_sat_ = true;
185 }
186 if (buffer_.starts_with("PwrDays")) {
187 if (this->boot_count_ <= 0) {
188 this->boot_count_ = 1;
189 } else {
190 this->boot_count_++;
191 }
192 this->cancel_interval("reboot");
193 this->no_response_count_ = 0;
194 ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
195
196 if (this->model_ == RG15) {
197 if (this->resolution_ == FORCE_HIGH) {
198 this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
199 } else {
200 this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode
201 }
202 }
203
204 if (this->model_ == RG9) {
205 this->write_str("P\n"); // set sensor to (P)polling mode
206
207 if (this->disable_led_) {
208 this->write_str("D 1\n"); // set sensor (D 1)rain detection LED disabled
209 } else {
210 this->write_str("D 0\n"); // set sensor (D 0)rain detection LED enabled
211 }
212 }
213 return;
214 }
215 if (buffer_.starts_with("SW")) {
216 std::string::size_type majend = this->buffer_.find('.');
217 std::string::size_type endversion = this->buffer_.find(' ', 3);
218 if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
219 ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
220 }
221 int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
222 int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
223
224 if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
225 ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
226 }
227 this->sw_version_ = major * 1000 + minor;
228 ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
229 return;
230 }
231 bool is_data_line = false;
232 for (int i = 0; i < NUM_SENSORS; i++) {
233 if (this->sensors_[i] != nullptr && this->buffer_.find(PROTOCOL_NAMES[i]) != std::string::npos) {
234 is_data_line = true;
235 break;
236 }
237 }
238 if (is_data_line) {
239 std::string::size_type tc = this->buffer_.find("TooCold");
240 this->too_cold_ |= tc != std::string::npos;
241 if (this->too_cold_) {
242 ESP_LOGD(TAG, "Received TooCold");
243 }
244 for (int i = 0; i < NUM_SENSORS; i++) {
245 if (this->sensors_[i] == nullptr) {
246 continue;
247 }
248 std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
249 if (n == std::string::npos) {
250 continue;
251 }
252
253 if (n == this->buffer_.find('t', n)) {
254 // The device temperature ('t') response contains both °C and °F values:
255 // "t 72F 22C".
256 // ESPHome uses only °C, only parse °C value (move past 'F').
257 n = this->buffer_.find('F', n);
258 if (n == std::string::npos) {
259 continue;
260 }
261 n += 1; // move past 'F'
262 } else {
263 n += strlen(PROTOCOL_NAMES[i]); // move past protocol name
264 }
265
266 // parse value, starting at str position n
267 float data = strtof(this->buffer_.substr(n).c_str(), nullptr);
268 this->sensors_[i]->publish_state(data);
269 ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
270 this->sensors_received_ |= (1 << i);
271 }
272 if (this->request_temperature_ && this->num_sensors_missing_() == 1) {
273 this->write_str("T\n");
274 }
275 } else {
276 for (const auto *ignore : IGNORE_STRINGS) {
277 if (buffer_.starts_with(ignore)) {
278 ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
279 return;
280 }
281 }
282 ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
283 }
284}
285
286} // namespace esphome::hydreon_rgxx
void mark_failed()
Mark this component as failed.
bool is_failed() const
Definition component.h:284
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") void set_interval(const std voi set_interval)(const char *name, uint32_t interval, std::function< void()> &&f)
Set an interval function with a unique name.
Definition component.h:417
ESPDEPRECATED("Use const char* or uint32_t overload instead. Removed in 2026.7.0", "2026.1.0") bool cancel_interval(const std boo cancel_interval)(const char *name)
Cancel an interval function.
Definition component.h:439
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 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:68
void check_uart_settings(uint32_t baud_rate, uint8_t stop_bits=1, UARTParityOptions parity=UART_CONFIG_PARITY_NONE, uint8_t data_bits=8)
Check that the configuration of the UART bus matches the provided values and otherwise print a warnin...
Definition uart.cpp:16
void write_str(const char *str)
Definition uart.h:32
bool read_byte(uint8_t *data)
Definition uart.h:34