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