diff --git a/esphome/components/kmeteriso/__init__.py b/esphome/components/kmeteriso/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/kmeteriso/kmeteriso.cpp b/esphome/components/kmeteriso/kmeteriso.cpp new file mode 100644 index 0000000000..0276ab3f67 --- /dev/null +++ b/esphome/components/kmeteriso/kmeteriso.cpp @@ -0,0 +1,82 @@ +#include "kmeteriso.h" +#include "esphome/core/hal.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace kmeteriso { + +static const char *const TAG = "kmeteriso.sensor"; + +static const uint8_t KMETER_ERROR_STATUS_REG = 0x20; +static const uint8_t KMETER_TEMP_VAL_REG = 0x00; +static const uint8_t KMETER_INTERNAL_TEMP_VAL_REG = 0x10; +static const uint8_t KMETER_FIRMWARE_VERSION_REG = 0xFE; + +void KMeterISOComponent::setup() { + ESP_LOGCONFIG(TAG, "Setting up KMeterISO..."); + this->error_code_ = NONE; + + // Mark as not failed before initializing. Some devices will turn off sensors to save on batteries + // and when they come back on, the COMPONENT_STATE_FAILED bit must be unset on the component. + if ((this->component_state_ & COMPONENT_STATE_MASK) == COMPONENT_STATE_FAILED) { + this->component_state_ &= ~COMPONENT_STATE_MASK; + this->component_state_ |= COMPONENT_STATE_CONSTRUCTION; + } + + auto err = this->bus_->writev(this->address_, nullptr, 0); + if (err == esphome::i2c::ERROR_OK) { + ESP_LOGCONFIG(TAG, "Could write to the address %d.", this->address_); + } else { + ESP_LOGCONFIG(TAG, "Could not write to the address %d.", this->address_); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + + uint8_t read_buf[4] = {1}; + if (!this->read_bytes(KMETER_ERROR_STATUS_REG, read_buf, 1)) { + ESP_LOGCONFIG(TAG, "Could not read from the device."); + this->error_code_ = COMMUNICATION_FAILED; + this->mark_failed(); + return; + } + if (read_buf[0] != 0) { + ESP_LOGCONFIG(TAG, "The device is not ready."); + this->error_code_ = STATUS_FAILED; + this->mark_failed(); + return; + } + ESP_LOGCONFIG(TAG, "The device was successfully setup."); +} + +float KMeterISOComponent::get_setup_priority() const { return setup_priority::DATA; } + +void KMeterISOComponent::update() { + uint8_t read_buf[4]; + + if (this->temperature_sensor_ != nullptr) { + if (!this->read_bytes(KMETER_TEMP_VAL_REG, read_buf, 4)) { + ESP_LOGW(TAG, "Error reading temperature."); + } else { + int32_t temp = encode_uint32(read_buf[3], read_buf[2], read_buf[1], read_buf[0]); + float temp_f = temp / 100.0; + ESP_LOGV(TAG, "Got temperature=%.2f °C", temp_f); + this->temperature_sensor_->publish_state(temp_f); + } + } + + if (this->internal_temperature_sensor_ != nullptr) { + if (!this->read_bytes(KMETER_INTERNAL_TEMP_VAL_REG, read_buf, 4)) { + ESP_LOGW(TAG, "Error reading internal temperature."); + return; + } else { + int32_t internal_temp = encode_uint32(read_buf[3], read_buf[2], read_buf[1], read_buf[0]); + float internal_temp_f = internal_temp / 100.0; + ESP_LOGV(TAG, "Got internal temperature=%.2f °C", internal_temp_f); + this->internal_temperature_sensor_->publish_state(internal_temp_f); + } + } +} + +} // namespace kmeteriso +} // namespace esphome diff --git a/esphome/components/kmeteriso/kmeteriso.h b/esphome/components/kmeteriso/kmeteriso.h new file mode 100644 index 0000000000..c8bed662b0 --- /dev/null +++ b/esphome/components/kmeteriso/kmeteriso.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/i2c/i2c_bus.h" + +namespace esphome { +namespace kmeteriso { + +/// This class implements support for the KMeterISO thermocouple sensor. +class KMeterISOComponent : public PollingComponent, public i2c::I2CDevice { + public: + void set_temperature_sensor(sensor::Sensor *t) { this->temperature_sensor_ = t; } + void set_internal_temperature_sensor(sensor::Sensor *t) { this->internal_temperature_sensor_ = t; } + + // ========== INTERNAL METHODS ========== + // (In most use cases you won't need these) + void setup() override; + float get_setup_priority() const override; + void update() override; + + protected: + sensor::Sensor *temperature_sensor_{nullptr}; + sensor::Sensor *internal_temperature_sensor_{nullptr}; + enum ErrorCode { + NONE = 0, + COMMUNICATION_FAILED, + STATUS_FAILED, + } error_code_{NONE}; +}; + +} // namespace kmeteriso +} // namespace esphome diff --git a/esphome/components/kmeteriso/sensor.py b/esphome/components/kmeteriso/sensor.py new file mode 100644 index 0000000000..e730e446ae --- /dev/null +++ b/esphome/components/kmeteriso/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_TEMPERATURE, + DEVICE_CLASS_TEMPERATURE, + STATE_CLASS_MEASUREMENT, + UNIT_CELSIUS, + ENTITY_CATEGORY_DIAGNOSTIC, +) + +CONF_INTERNAL_TEMPERATURE = "internal_temperature" +DEPENDENCIES = ["i2c"] + +kmeteriso_ns = cg.esphome_ns.namespace("kmeteriso") + +KMeterISOComponent = kmeteriso_ns.class_( + "KMeterISOComponent", cg.PollingComponent, i2c.I2CDevice +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(KMeterISOComponent), + cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=2, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_INTERNAL_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x66)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await i2c.register_i2c_device(var, config) + + if temperature_config := config.get(CONF_TEMPERATURE): + sens = await sensor.new_sensor(temperature_config) + cg.add(var.set_temperature_sensor(sens)) + if internal_temperature_config := config.get(CONF_INTERNAL_TEMPERATURE): + sens = await sensor.new_sensor(internal_temperature_config) + cg.add(var.set_internal_temperature_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index a00b886ac1..2419634570 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -792,6 +792,13 @@ sensor: name: INA3221 Channel 1 Shunt Voltage update_interval: 15s i2c_id: i2c_bus + - platform: kmeteriso + temperature: + name: Outside Temperature + internal_temperature: + name: Internal Ttemperature + update_interval: 15s + i2c_id: i2c_bus - platform: kalman_combinator name: Kalman-filtered temperature process_std_dev: 0.00139