diff --git a/esphome/components/hydreon_rgxx/binary_sensor.py b/esphome/components/hydreon_rgxx/binary_sensor.py index 0d489ebcb7..776be8a5d8 100644 --- a/esphome/components/hydreon_rgxx/binary_sensor.py +++ b/esphome/components/hydreon_rgxx/binary_sensor.py @@ -4,12 +4,15 @@ from esphome.components import binary_sensor from esphome.const import ( CONF_ID, DEVICE_CLASS_COLD, + DEVICE_CLASS_PROBLEM, ) from . import hydreon_rgxx_ns, HydreonRGxxComponent CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id" CONF_TOO_COLD = "too_cold" +CONF_LENS_BAD = "lens_bad" +CONF_EM_SAT = "em_sat" HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_( "HydreonRGxxBinaryComponent", cg.Component @@ -23,6 +26,12 @@ CONFIG_SCHEMA = cv.Schema( cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema( device_class=DEVICE_CLASS_COLD ), + cv.Optional(CONF_LENS_BAD): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + ), + cv.Optional(CONF_EM_SAT): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_PROBLEM, + ), } ) @@ -31,6 +40,14 @@ async def to_code(config): main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID]) bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor) await cg.register_component(bin_component, config) - if CONF_TOO_COLD in config: - tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD]) - cg.add(main_sensor.set_too_cold_sensor(tc)) + + mapping = { + CONF_TOO_COLD: main_sensor.set_too_cold_sensor, + CONF_LENS_BAD: main_sensor.set_lens_bad_sensor, + CONF_EM_SAT: main_sensor.set_em_sat_sensor, + } + + for key, value in mapping.items(): + if key in config: + sensor = await binary_sensor.new_binary_sensor(config[key]) + cg.add(value(sensor)) diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp index 25d2617677..da4345e136 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.cpp @@ -9,6 +9,7 @@ static const int MAX_DATA_LENGTH_BYTES = 80; static const uint8_t ASCII_LF = 0x0A; #define HYDREON_RGXX_COMMA , static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)}; +static const char *const IGNORE_STRINGS[] = {HYDREON_RGXX_IGNORE_LIST(, HYDREON_RGXX_COMMA)}; void HydreonRGxxComponent::dump_config() { this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8); @@ -34,33 +35,37 @@ void HydreonRGxxComponent::setup() { this->schedule_reboot_(); } -bool HydreonRGxxComponent::sensor_missing_() { +int HydreonRGxxComponent::num_sensors_missing_() { if (this->sensors_received_ == -1) { - // no request sent yet, don't check - return false; - } else { - if (this->sensors_received_ == 0) { - ESP_LOGW(TAG, "No data at all"); - return true; - } - for (int i = 0; i < NUM_SENSORS; i++) { - if (this->sensors_[i] == nullptr) { - continue; - } - if ((this->sensors_received_ >> i & 1) == 0) { - ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); - return true; - } - } - return false; + return -1; } + int ret = NUM_SENSORS; + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + ret -= 1; + continue; + } + if ((this->sensors_received_ >> i & 1) != 0) { + ret -= 1; + } + } + return ret; } void HydreonRGxxComponent::update() { if (this->boot_count_ > 0) { - if (this->sensor_missing_()) { + if (this->num_sensors_missing_() > 0) { + for (int i = 0; i < NUM_SENSORS; i++) { + if (this->sensors_[i] == nullptr) { + continue; + } + if ((this->sensors_received_ >> i & 1) == 0) { + ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]); + } + } + this->no_response_count_++; - ESP_LOGE(TAG, "data missing %d times", this->no_response_count_); + ESP_LOGE(TAG, "missing %d sensors; %d times in a row", this->num_sensors_missing_(), this->no_response_count_); if (this->no_response_count_ > 15) { ESP_LOGE(TAG, "asking sensor to reboot"); for (auto &sensor : this->sensors_) { @@ -79,8 +84,16 @@ void HydreonRGxxComponent::update() { if (this->too_cold_sensor_ != nullptr) { this->too_cold_sensor_->publish_state(this->too_cold_); } + if (this->lens_bad_sensor_ != nullptr) { + this->lens_bad_sensor_->publish_state(this->lens_bad_); + } + if (this->em_sat_sensor_ != nullptr) { + this->em_sat_sensor_->publish_state(this->em_sat_); + } #endif this->too_cold_ = false; + this->lens_bad_ = false; + this->em_sat_ = false; this->sensors_received_ = 0; } } @@ -146,6 +159,25 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); return; } + std::string::size_type newlineposn = this->buffer_.find('\n'); + if (newlineposn <= 1) { + // allow both \r\n and \n + ESP_LOGD(TAG, "Received empty line"); + return; + } + if (newlineposn <= 2) { + // single character lines, such as acknowledgements + ESP_LOGD(TAG, "Received ack: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + return; + } + if (this->buffer_.find("LensBad") != std::string::npos) { + ESP_LOGW(TAG, "Received LensBad!"); + this->lens_bad_ = true; + } + if (this->buffer_.find("EmSat") != std::string::npos) { + ESP_LOGW(TAG, "Received EmSat!"); + this->em_sat_ = true; + } if (this->buffer_starts_with_("PwrDays")) { if (this->boot_count_ <= 0) { this->boot_count_ = 1; @@ -200,7 +232,16 @@ void HydreonRGxxComponent::process_line_() { ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state()); this->sensors_received_ |= (1 << i); } + if (this->request_temperature_ && this->num_sensors_missing_() == 1) { + this->write_str("T\n"); + } } else { + for (const auto *ignore : IGNORE_STRINGS) { + if (this->buffer_starts_with_(ignore)) { + ESP_LOGI(TAG, "Ignoring %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str()); + return; + } + } ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str()); } } diff --git a/esphome/components/hydreon_rgxx/hydreon_rgxx.h b/esphome/components/hydreon_rgxx/hydreon_rgxx.h index ebe4a35b19..1697060d28 100644 --- a/esphome/components/hydreon_rgxx/hydreon_rgxx.h +++ b/esphome/components/hydreon_rgxx/hydreon_rgxx.h @@ -26,13 +26,18 @@ static const uint8_t NUM_SENSORS = 1; #define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("") #endif +#define HYDREON_RGXX_IGNORE_LIST(F, SEP) F("Emitters") SEP F("Event") SEP F("Reset") + class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { public: void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; } #ifdef USE_BINARY_SENSOR void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; } + void set_lens_bad_sensor(binary_sensor::BinarySensor *sensor) { this->lens_bad_sensor_ = sensor; } + void set_em_sat_sensor(binary_sensor::BinarySensor *sensor) { this->em_sat_sensor_ = sensor; } #endif void set_model(RGModel model) { model_ = model; } + void set_request_temperature(bool b) { request_temperature_ = b; } /// Schedule data readings. void update() override; @@ -49,11 +54,13 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { void schedule_reboot_(); bool buffer_starts_with_(const std::string &prefix); bool buffer_starts_with_(const char *prefix); - bool sensor_missing_(); + int num_sensors_missing_(); sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr}; #ifdef USE_BINARY_SENSOR binary_sensor::BinarySensor *too_cold_sensor_ = nullptr; + binary_sensor::BinarySensor *lens_bad_sensor_ = nullptr; + binary_sensor::BinarySensor *em_sat_sensor_ = nullptr; #endif int16_t boot_count_ = 0; @@ -62,6 +69,9 @@ class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice { RGModel model_ = RG9; int sw_version_ = 0; bool too_cold_ = false; + bool lens_bad_ = false; + bool em_sat_ = false; + bool request_temperature_ = false; // bit field showing which sensors we have received data for int sensors_received_ = -1; diff --git a/esphome/components/hydreon_rgxx/sensor.py b/esphome/components/hydreon_rgxx/sensor.py index c604f8d3c1..730499a493 100644 --- a/esphome/components/hydreon_rgxx/sensor.py +++ b/esphome/components/hydreon_rgxx/sensor.py @@ -5,8 +5,11 @@ from esphome.const import ( CONF_ID, CONF_MODEL, CONF_MOISTURE, + CONF_TEMPERATURE, DEVICE_CLASS_HUMIDITY, STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ICON_THERMOMETER, ) from . import RGModel, HydreonRGxxComponent @@ -33,6 +36,7 @@ SUPPORTED_SENSORS = { CONF_TOTAL_ACC: ["RG_15"], CONF_R_INT: ["RG_15"], CONF_MOISTURE: ["RG_9"], + CONF_TEMPERATURE: ["RG_9"], } PROTOCOL_NAMES = { CONF_MOISTURE: "R", @@ -40,6 +44,7 @@ PROTOCOL_NAMES = { CONF_R_INT: "RInt", CONF_EVENT_ACC: "EventAcc", CONF_TOTAL_ACC: "TotalAcc", + CONF_TEMPERATURE: "t", } @@ -92,6 +97,12 @@ CONFIG_SCHEMA = cv.All( device_class=DEVICE_CLASS_HUMIDITY, state_class=STATE_CLASS_MEASUREMENT, ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=0, + icon=ICON_THERMOMETER, + state_class=STATE_CLASS_MEASUREMENT, + ), } ) .extend(cv.polling_component_schema("60s")) @@ -108,7 +119,7 @@ async def to_code(config): cg.add_define( "HYDREON_RGXX_PROTOCOL_LIST(F, sep)", cg.RawExpression( - " sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()]) + " sep ".join([f'F("{name} ")' for name in PROTOCOL_NAMES.values()]) ), ) cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES)) @@ -117,3 +128,5 @@ async def to_code(config): if conf in config: sens = await sensor.new_sensor(config[conf]) cg.add(var.set_sensor(sens, i)) + + cg.add(var.set_request_temperature(CONF_TEMPERATURE in config)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 1abbee8dc5..4b2224dc3c 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -866,6 +866,10 @@ binary_sensor: hydreon_rgxx_id: "hydreon_rg9" too_cold: name: "rg9_toocold" + em_sat: + name: "rg9_emsat" + lens_bad: + name: "rg9_lens_bad" - platform: template id: 'pzemac_reset_energy' on_press: