ESPHome 2025.5.0
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, " Model: RG9");
22 ESP_LOGCONFIG(TAG, " Disable Led: %s", 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 ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx...");
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
160bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
161 return this->buffer_starts_with_(prefix.c_str());
162}
163
164bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; }
165
167 ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
168
169 if (buffer_[0] == ';') {
170 ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
171 return;
172 }
173 std::string::size_type newlineposn = this->buffer_.find('\n');
174 if (newlineposn <= 1) {
175 // allow both \r\n and \n
176 ESP_LOGD(TAG, "Received empty line");
177 return;
178 }
179 if (newlineposn <= 2) {
180 // single character lines, such as acknowledgements
181 ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
182 return;
183 }
184 if (this->buffer_.find("LensBad") != std::string::npos) {
185 ESP_LOGW(TAG, "Received LensBad!");
186 this->lens_bad_ = true;
187 }
188 if (this->buffer_.find("EmSat") != std::string::npos) {
189 ESP_LOGW(TAG, "Received EmSat!");
190 this->em_sat_ = true;
191 }
192 if (this->buffer_starts_with_("PwrDays")) {
193 if (this->boot_count_ <= 0) {
194 this->boot_count_ = 1;
195 } else {
196 this->boot_count_++;
197 }
198 this->cancel_interval("reboot");
199 this->no_response_count_ = 0;
200 ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
201
202 if (this->model_ == RG15) {
203 if (this->resolution_ == FORCE_HIGH) {
204 this->write_str("P\nH\nM\n"); // set sensor to (P)polling mode, (H)high res mode, (M)metric mode
205 } else {
206 this->write_str("P\nL\nM\n"); // set sensor to (P)polling mode, (L)low res mode, (M)metric mode
207 }
208 }
209
210 if (this->model_ == RG9) {
211 this->write_str("P\n"); // set sensor to (P)polling mode
212
213 if (this->disable_led_) {
214 this->write_str("D 1\n"); // set sensor (D 1)rain detection LED disabled
215 } else {
216 this->write_str("D 0\n"); // set sensor (D 0)rain detection LED enabled
217 }
218 }
219 return;
220 }
221 if (this->buffer_starts_with_("SW")) {
222 std::string::size_type majend = this->buffer_.find('.');
223 std::string::size_type endversion = this->buffer_.find(' ', 3);
224 if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
225 ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
226 }
227 int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
228 int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
229
230 if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
231 ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
232 }
233 this->sw_version_ = major * 1000 + minor;
234 ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
235 return;
236 }
237 bool is_data_line = false;
238 for (int i = 0; i < NUM_SENSORS; i++) {
239 if (this->sensors_[i] != nullptr && this->buffer_.find(PROTOCOL_NAMES[i]) != std::string::npos) {
240 is_data_line = true;
241 break;
242 }
243 }
244 if (is_data_line) {
245 std::string::size_type tc = this->buffer_.find("TooCold");
246 this->too_cold_ |= tc != std::string::npos;
247 if (this->too_cold_) {
248 ESP_LOGD(TAG, "Received TooCold");
249 }
250 for (int i = 0; i < NUM_SENSORS; i++) {
251 if (this->sensors_[i] == nullptr) {
252 continue;
253 }
254 std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
255 if (n == std::string::npos) {
256 continue;
257 }
258
259 if (n == this->buffer_.find('t', n)) {
260 // The device temperature ('t') response contains both °C and °F values:
261 // "t 72F 22C".
262 // ESPHome uses only °C, only parse °C value (move past 'F').
263 n = this->buffer_.find('F', n);
264 if (n == std::string::npos) {
265 continue;
266 }
267 n += 1; // move past 'F'
268 } else {
269 n += strlen(PROTOCOL_NAMES[i]); // move past protocol name
270 }
271
272 // parse value, starting at str position n
273 float data = strtof(this->buffer_.substr(n).c_str(), nullptr);
274 this->sensors_[i]->publish_state(data);
275 ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
276 this->sensors_received_ |= (1 << i);
277 }
278 if (this->request_temperature_ && this->num_sensors_missing_() == 1) {
279 this->write_str("T\n");
280 }
281 } else {
282 for (const auto *ignore : IGNORE_STRINGS) {
283 if (this->buffer_starts_with_(ignore)) {
284 ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
285 return;
286 }
287 }
288 ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
289 }
290}
291
293
294} // namespace hydreon_rgxx
295} // 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:55
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition component.cpp:59
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:19
Providing packet encoding functions for exchanging data with a remote host.
Definition a01nyub.cpp:7