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