From 350d4e5071c21ed3acc0fb6b4c6d571dfc35efd9 Mon Sep 17 00:00:00 2001 From: Samuel Sieb Date: Wed, 22 Feb 2023 17:31:35 -0800 Subject: [PATCH] add kuntze component (#4411) * add kuntze component * fixes * more lint --------- Co-authored-by: Samuel Sieb --- CODEOWNERS | 1 + esphome/components/kuntze/__init__.py | 0 esphome/components/kuntze/kuntze.cpp | 91 +++++++++++++++++++ esphome/components/kuntze/kuntze.h | 42 +++++++++ esphome/components/kuntze/sensor.py | 123 ++++++++++++++++++++++++++ tests/test3.yaml | 6 ++ tests/test5.yaml | 6 ++ 7 files changed, 269 insertions(+) create mode 100644 esphome/components/kuntze/__init__.py create mode 100644 esphome/components/kuntze/kuntze.cpp create mode 100644 esphome/components/kuntze/kuntze.h create mode 100644 esphome/components/kuntze/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index d24a19adb1..00f207862a 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -119,6 +119,7 @@ esphome/components/json/* @OttoWinter esphome/components/kalman_combinator/* @Cat-Ion esphome/components/key_collector/* @ssieb esphome/components/key_provider/* @ssieb +esphome/components/kuntze/* @ssieb esphome/components/lcd_menu/* @numo68 esphome/components/ld2410/* @sebcaps esphome/components/ledc/* @OttoWinter diff --git a/esphome/components/kuntze/__init__.py b/esphome/components/kuntze/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/kuntze/kuntze.cpp b/esphome/components/kuntze/kuntze.cpp new file mode 100644 index 0000000000..e50dafca86 --- /dev/null +++ b/esphome/components/kuntze/kuntze.cpp @@ -0,0 +1,91 @@ +#include "kuntze.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace kuntze { + +static const char *const TAG = "kuntze"; + +static const uint8_t CMD_READ_REG = 0x03; +static const uint16_t REGISTER[] = {4136, 4160, 4680, 6000, 4688, 4728, 5832}; + +void Kuntze::on_modbus_data(const std::vector &data) { + auto get_16bit = [&](int i) -> uint16_t { return (uint16_t(data[i * 2]) << 8) | uint16_t(data[i * 2 + 1]); }; + + this->waiting_ = false; + ESP_LOGV(TAG, "Data: %s", hexencode(data).c_str()); + + float value = (float) get_16bit(0); + for (int i = 0; i < data[3]; i++) + value /= 10.0; + switch (this->state_) { + case 1: + ESP_LOGD(TAG, "pH=%.1f", value); + if (this->ph_sensor_ != nullptr) + this->ph_sensor_->publish_state(value); + break; + case 2: + ESP_LOGD(TAG, "temperature=%.1f", value); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(value); + break; + case 3: + ESP_LOGD(TAG, "DIS1=%.1f", value); + if (this->dis1_sensor_ != nullptr) + this->dis1_sensor_->publish_state(value); + break; + case 4: + ESP_LOGD(TAG, "DIS2=%.1f", value); + if (this->dis2_sensor_ != nullptr) + this->dis2_sensor_->publish_state(value); + break; + case 5: + ESP_LOGD(TAG, "REDOX=%.1f", value); + if (this->redox_sensor_ != nullptr) + this->redox_sensor_->publish_state(value); + break; + case 6: + ESP_LOGD(TAG, "EC=%.1f", value); + if (this->ec_sensor_ != nullptr) + this->ec_sensor_->publish_state(value); + break; + case 7: + ESP_LOGD(TAG, "OCI=%.1f", value); + if (this->oci_sensor_ != nullptr) + this->oci_sensor_->publish_state(value); + break; + } + if (++this->state_ > 7) + this->state_ = 0; +} + +void Kuntze::loop() { + uint32_t now = millis(); + // timeout after 15 seconds + if (this->waiting_ && (now - this->last_send_ > 15000)) { + ESP_LOGW(TAG, "timed out waiting for response"); + this->waiting_ = false; + } + if (this->waiting_ || (this->state_ == 0)) + return; + this->last_send_ = now; + send(CMD_READ_REG, REGISTER[this->state_ - 1], 2); + this->waiting_ = true; +} + +void Kuntze::update() { this->state_ = 1; } + +void Kuntze::dump_config() { + ESP_LOGCONFIG(TAG, "Kuntze:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + LOG_SENSOR("", "pH", this->ph_sensor_); + LOG_SENSOR("", "temperature", this->temperature_sensor_); + LOG_SENSOR("", "DIS1", this->dis1_sensor_); + LOG_SENSOR("", "DIS2", this->dis2_sensor_); + LOG_SENSOR("", "REDOX", this->redox_sensor_); + LOG_SENSOR("", "EC", this->ec_sensor_); + LOG_SENSOR("", "OCI", this->oci_sensor_); +} + +} // namespace kuntze +} // namespace esphome diff --git a/esphome/components/kuntze/kuntze.h b/esphome/components/kuntze/kuntze.h new file mode 100644 index 0000000000..aad7c1cbbf --- /dev/null +++ b/esphome/components/kuntze/kuntze.h @@ -0,0 +1,42 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/modbus/modbus.h" + +namespace esphome { +namespace kuntze { + +class Kuntze : public PollingComponent, public modbus::ModbusDevice { + public: + void set_ph_sensor(sensor::Sensor *ph_sensor) { ph_sensor_ = ph_sensor; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_dis1_sensor(sensor::Sensor *dis1_sensor) { dis1_sensor_ = dis1_sensor; } + void set_dis2_sensor(sensor::Sensor *dis2_sensor) { dis2_sensor_ = dis2_sensor; } + void set_redox_sensor(sensor::Sensor *redox_sensor) { redox_sensor_ = redox_sensor; } + void set_ec_sensor(sensor::Sensor *ec_sensor) { ec_sensor_ = ec_sensor; } + void set_oci_sensor(sensor::Sensor *oci_sensor) { oci_sensor_ = oci_sensor; } + + void loop() override; + void update() override; + + void on_modbus_data(const std::vector &data) override; + + void dump_config() override; + + protected: + int state_{0}; + bool waiting_{false}; + uint32_t last_send_{0}; + + sensor::Sensor *ph_sensor_{nullptr}; + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *dis1_sensor_{nullptr}; + sensor::Sensor *dis2_sensor_{nullptr}; + sensor::Sensor *redox_sensor_{nullptr}; + sensor::Sensor *ec_sensor_{nullptr}; + sensor::Sensor *oci_sensor_{nullptr}; +}; + +} // namespace kuntze +} // namespace esphome diff --git a/esphome/components/kuntze/sensor.py b/esphome/components/kuntze/sensor.py new file mode 100644 index 0000000000..96c874fa5c --- /dev/null +++ b/esphome/components/kuntze/sensor.py @@ -0,0 +1,123 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, modbus +from esphome.const import ( + CONF_ID, + CONF_EC, + CONF_PH, + CONF_TEMPERATURE, + ICON_EMPTY, + ICON_THERMOMETER, + UNIT_CELSIUS, + UNIT_EMPTY, + UNIT_PH, + STATE_CLASS_MEASUREMENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_TEMPERATURE, +) + +CODEOWNERS = ["@ssieb"] + +AUTO_LOAD = ["modbus"] + +kuntze_ns = cg.esphome_ns.namespace("kuntze") +Kuntze = kuntze_ns.class_("Kuntze", cg.PollingComponent, modbus.ModbusDevice) + +CONF_DIS1 = "dis1" +CONF_DIS2 = "dis2" +CONF_REDOX = "redox" +CONF_OCI = "oci" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(Kuntze), + cv.Optional(CONF_PH): sensor.sensor_schema( + unit_of_measurement=UNIT_PH, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_THERMOMETER, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_TEMPERATURE, + ), + cv.Optional(CONF_DIS1): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_DIS2): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_REDOX): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_EC): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + cv.Optional(CONF_OCI): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=1, + state_class=STATE_CLASS_MEASUREMENT, + device_class=DEVICE_CLASS_EMPTY, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(modbus.modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await modbus.register_modbus_device(var, config) + + if CONF_PH in config: + conf = config[CONF_PH] + sens = await sensor.new_sensor(conf) + cg.add(var.set_ph_sensor(sens)) + if CONF_TEMPERATURE in config: + conf = config[CONF_TEMPERATURE] + sens = await sensor.new_sensor(conf) + cg.add(var.set_temperature_sensor(sens)) + if CONF_DIS1 in config: + conf = config[CONF_DIS1] + sens = await sensor.new_sensor(conf) + cg.add(var.set_dis1_sensor(sens)) + if CONF_DIS2 in config: + conf = config[CONF_DIS2] + sens = await sensor.new_sensor(conf) + cg.add(var.set_dis2_sensor(sens)) + if CONF_REDOX in config: + conf = config[CONF_REDOX] + sens = await sensor.new_sensor(conf) + cg.add(var.set_redox_sensor(sens)) + if CONF_EC in config: + conf = config[CONF_EC] + sens = await sensor.new_sensor(conf) + cg.add(var.set_ec_sensor(sens)) + if CONF_OCI in config: + conf = config[CONF_OCI] + sens = await sensor.new_sensor(conf) + cg.add(var.set_oci_sensor(sens)) diff --git a/tests/test3.yaml b/tests/test3.yaml index 4827b7cbcd..6755be9f14 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -807,6 +807,12 @@ sensor: temperature_1: name: Temperature 1 + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature + time: - platform: homeassistant diff --git a/tests/test5.yaml b/tests/test5.yaml index 21419692e4..5708f1f093 100644 --- a/tests/test5.yaml +++ b/tests/test5.yaml @@ -520,6 +520,12 @@ sensor: name: VBus Custom Sensor lambda: return x[0] / 10.0; + - platform: kuntze + ph: + name: Kuntze pH + temperature: + name: Kuntze temperature + script: - id: automation_test then: