ESPHome  2024.4.0
hydreon_rgxx.cpp
Go to the documentation of this file.
1 #include "hydreon_rgxx.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace hydreon_rgxx {
6 
7 static const char *const TAG = "hydreon_rgxx.sensor";
8 static const int MAX_DATA_LENGTH_BYTES = 80;
9 static const uint8_t ASCII_LF = 0x0A;
10 #define HYDREON_RGXX_COMMA ,
11 static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
12 static 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) {
102  this->em_sat_sensor_->publish_state(this->em_sat_);
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 
160 bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
161  return this->buffer_starts_with_(prefix.c_str());
162 }
163 
164 bool 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_starts_with_(PROTOCOL_NAMES[i])) {
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
void write_str(const char *str)
Definition: uart.h:27
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:19
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:52
binary_sensor::BinarySensor * too_cold_sensor_
Definition: hydreon_rgxx.h:69
binary_sensor::BinarySensor * lens_bad_sensor_
Definition: hydreon_rgxx.h:70
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition: component.cpp:56
void setup() override
Setup the sensor and test for a connection.
void schedule_reboot_()
Communication with the sensor is asynchronous.
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
bool read_byte(uint8_t *data)
Definition: uart.h:29
void publish_state(float state)
Publish a new state to the front-end.
Definition: sensor.cpp:39
void publish_state(bool state)
Publish a new state to the front-end.
virtual void mark_failed()
Mark this component as failed.
Definition: component.cpp:118
sensor::Sensor * sensors_[NUM_SENSORS]
Definition: hydreon_rgxx.h:67
This is a workaround until we can figure out a way to get the tflite-micro idf component code availab...
Definition: a01nyub.cpp:7
binary_sensor::BinarySensor * em_sat_sensor_
Definition: hydreon_rgxx.h:71
void loop() override
Read data once available.
bool buffer_starts_with_(const std::string &prefix)
void update() override
Schedule data readings.