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