ESPHome  2023.8.3
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  LOG_UPDATE_INTERVAL(this);
21 
22  int i = 0;
23 #define HYDREON_RGXX_LOG_SENSOR(s) \
24  if (this->sensors_[i++] != nullptr) { \
25  LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
26  }
27  HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
28 }
29 
31  ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx...");
32  while (this->available() != 0) {
33  this->read();
34  }
35  this->schedule_reboot_();
36 }
37 
39  if (this->sensors_received_ == -1) {
40  return -1;
41  }
42  int ret = NUM_SENSORS;
43  for (int i = 0; i < NUM_SENSORS; i++) {
44  if (this->sensors_[i] == nullptr) {
45  ret -= 1;
46  continue;
47  }
48  if ((this->sensors_received_ >> i & 1) != 0) {
49  ret -= 1;
50  }
51  }
52  return ret;
53 }
54 
56  if (this->boot_count_ > 0) {
57  if (this->num_sensors_missing_() > 0) {
58  for (int i = 0; i < NUM_SENSORS; i++) {
59  if (this->sensors_[i] == nullptr) {
60  continue;
61  }
62  if ((this->sensors_received_ >> i & 1) == 0) {
63  ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
64  }
65  }
66 
67  this->no_response_count_++;
68  ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_);
69  if (this->no_response_count_ > 15) {
70  ESP_LOGE(TAG, "asking sensor to reboot");
71  for (auto &sensor : this->sensors_) {
72  if (sensor != nullptr) {
73  sensor->publish_state(NAN);
74  }
75  }
76  this->schedule_reboot_();
77  return;
78  }
79  } else {
80  this->no_response_count_ = 0;
81  }
82  this->write_str("R\n");
83 #ifdef USE_BINARY_SENSOR
84  if (this->too_cold_sensor_ != nullptr) {
86  }
87  if (this->lens_bad_sensor_ != nullptr) {
89  }
90  if (this->em_sat_sensor_ != nullptr) {
91  this->em_sat_sensor_->publish_state(this->em_sat_);
92  }
93 #endif
94  this->too_cold_ = false;
95  this->lens_bad_ = false;
96  this->em_sat_ = false;
97  this->sensors_received_ = 0;
98  }
99 }
100 
102  uint8_t data;
103  while (this->available() > 0) {
104  if (this->read_byte(&data)) {
105  buffer_ += (char) data;
106  if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) {
107  // complete line received
108  this->process_line_();
109  this->buffer_.clear();
110  }
111  }
112  }
113 }
114 
130  this->boot_count_ = 0;
131  this->set_interval("reboot", 5000, [this]() {
132  if (this->boot_count_ < 0) {
133  ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
134  }
135  this->boot_count_--;
136  this->write_str("K\n");
137  if (this->boot_count_ < -5) {
138  ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
139  for (auto &sensor : this->sensors_) {
140  if (sensor != nullptr) {
141  sensor->publish_state(NAN);
142  }
143  }
144  this->mark_failed();
145  }
146  });
147 }
148 
149 bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
150  return this->buffer_starts_with_(prefix.c_str());
151 }
152 
153 bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; }
154 
156  ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
157 
158  if (buffer_[0] == ';') {
159  ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
160  return;
161  }
162  std::string::size_type newlineposn = this->buffer_.find('\n');
163  if (newlineposn <= 1) {
164  // allow both \r\n and \n
165  ESP_LOGD(TAG, "Received empty line");
166  return;
167  }
168  if (newlineposn <= 2) {
169  // single character lines, such as acknowledgements
170  ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
171  return;
172  }
173  if (this->buffer_.find("LensBad") != std::string::npos) {
174  ESP_LOGW(TAG, "Received LensBad!");
175  this->lens_bad_ = true;
176  }
177  if (this->buffer_.find("EmSat") != std::string::npos) {
178  ESP_LOGW(TAG, "Received EmSat!");
179  this->em_sat_ = true;
180  }
181  if (this->buffer_starts_with_("PwrDays")) {
182  if (this->boot_count_ <= 0) {
183  this->boot_count_ = 1;
184  } else {
185  this->boot_count_++;
186  }
187  this->cancel_interval("reboot");
188  this->no_response_count_ = 0;
189  ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
190  this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode
191  return;
192  }
193  if (this->buffer_starts_with_("SW")) {
194  std::string::size_type majend = this->buffer_.find('.');
195  std::string::size_type endversion = this->buffer_.find(' ', 3);
196  if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
197  ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
198  }
199  int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
200  int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
201 
202  if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
203  ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
204  }
205  this->sw_version_ = major * 1000 + minor;
206  ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
207  return;
208  }
209  bool is_data_line = false;
210  for (int i = 0; i < NUM_SENSORS; i++) {
211  if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) {
212  is_data_line = true;
213  break;
214  }
215  }
216  if (is_data_line) {
217  std::string::size_type tc = this->buffer_.find("TooCold");
218  this->too_cold_ |= tc != std::string::npos;
219  if (this->too_cold_) {
220  ESP_LOGD(TAG, "Received TooCold");
221  }
222  for (int i = 0; i < NUM_SENSORS; i++) {
223  if (this->sensors_[i] == nullptr) {
224  continue;
225  }
226  std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
227  if (n == std::string::npos) {
228  continue;
229  }
230  float data = strtof(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr);
231  this->sensors_[i]->publish_state(data);
232  ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
233  this->sensors_received_ |= (1 << i);
234  }
235  if (this->request_temperature_ && this->num_sensors_missing_() == 1) {
236  this->write_str("T\n");
237  }
238  } else {
239  for (const auto *ignore : IGNORE_STRINGS) {
240  if (this->buffer_starts_with_(ignore)) {
241  ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
242  return;
243  }
244  }
245  ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
246  }
247 }
248 
250 
251 } // namespace hydreon_rgxx
252 } // 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:18
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:51
binary_sensor::BinarySensor * too_cold_sensor_
Definition: hydreon_rgxx.h:61
binary_sensor::BinarySensor * lens_bad_sensor_
Definition: hydreon_rgxx.h:62
bool cancel_interval(const std::string &name)
Cancel an interval function.
Definition: component.cpp:55
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:112
sensor::Sensor * sensors_[NUM_SENSORS]
Definition: hydreon_rgxx.h:59
binary_sensor::BinarySensor * em_sat_sensor_
Definition: hydreon_rgxx.h:63
void loop() override
Read data once available.
bool buffer_starts_with_(const std::string &prefix)
void update() override
Schedule data readings.