From 4bf5faf80831e91f6ba8e24ff25ef1e5d3ad35ec Mon Sep 17 00:00:00 2001 From: Mathias Stock <89242257+Stock-M@users.noreply.github.com> Date: Tue, 17 Jan 2023 22:42:43 +0100 Subject: [PATCH] Add support for EE895 (#3771) --- CODEOWNERS | 1 + esphome/components/ee895/__init__.py | 0 esphome/components/ee895/ee895.cpp | 115 +++++++++++++++++++++++++++ esphome/components/ee895/ee895.h | 34 ++++++++ esphome/components/ee895/sensor.py | 69 ++++++++++++++++ tests/test1.yaml | 9 +++ 6 files changed, 228 insertions(+) create mode 100644 esphome/components/ee895/__init__.py create mode 100644 esphome/components/ee895/ee895.cpp create mode 100644 esphome/components/ee895/ee895.h create mode 100644 esphome/components/ee895/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 026a57a423..fb748c0e70 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -71,6 +71,7 @@ esphome/components/display_menu_base/* @numo68 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/dsmr/* @glmnet @zuidwijk +esphome/components/ee895/* @Stock-M esphome/components/ektf2232/* @jesserockz esphome/components/ens210/* @itn3rd77 esphome/components/esp32/* @esphome/core diff --git a/esphome/components/ee895/__init__.py b/esphome/components/ee895/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ee895/ee895.cpp b/esphome/components/ee895/ee895.cpp new file mode 100644 index 0000000000..a7186ffbbc --- /dev/null +++ b/esphome/components/ee895/ee895.cpp @@ -0,0 +1,115 @@ +#include "ee895.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace ee895 { + +static const char *const TAG = "ee895"; + +static const uint16_t CRC16_ONEWIRE_START = 0xFFFF; +static const uint8_t FUNCTION_CODE_READ = 0x03; +static const uint16_t SERIAL_NUMBER = 0x0000; +static const uint16_t TEMPERATURE_ADDRESS = 0x03EA; +static const uint16_t CO2_ADDRESS = 0x0424; +static const uint16_t PRESSURE_ADDRESS = 0x04B0; + +void EE895Component::setup() { + uint16_t crc16_check = 0; + ESP_LOGCONFIG(TAG, "Setting up EE895..."); + write_command_(SERIAL_NUMBER, 8); + uint8_t serial_number[20]; + this->read(serial_number, 20); + + crc16_check = (serial_number[19] << 8) + serial_number[18]; + if (crc16_check != calc_crc16_(serial_number, 19)) { + this->error_code_ = CRC_CHECK_FAILED; + this->mark_failed(); + return; + } + ESP_LOGV(TAG, " Serial Number: 0x%s", format_hex(serial_number + 2, 16).c_str()); +} + +void EE895Component::dump_config() { + ESP_LOGCONFIG(TAG, "EE895:"); + LOG_I2C_DEVICE(this); + switch (this->error_code_) { + case COMMUNICATION_FAILED: + ESP_LOGE(TAG, "Communication with EE895 failed!"); + break; + case CRC_CHECK_FAILED: + ESP_LOGE(TAG, "The crc check failed"); + break; + case NONE: + default: + break; + } + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); + LOG_SENSOR(" ", "CO2", this->co2_sensor_); + LOG_SENSOR(" ", "Pressure", this->pressure_sensor_); +} + +float EE895Component::get_setup_priority() const { return setup_priority::DATA; } + +void EE895Component::update() { + write_command_(TEMPERATURE_ADDRESS, 2); + this->set_timeout(50, [this]() { + float temperature = read_float_(); + + write_command_(CO2_ADDRESS, 2); + float co2 = read_float_(); + + write_command_(PRESSURE_ADDRESS, 2); + float pressure = read_float_(); + ESP_LOGD(TAG, "Got temperature=%.1f°C co2=%.0fppm pressure=%.1f%mbar", temperature, co2, pressure); + if (this->temperature_sensor_ != nullptr) + this->temperature_sensor_->publish_state(temperature); + if (this->co2_sensor_ != nullptr) + this->co2_sensor_->publish_state(co2); + if (this->pressure_sensor_ != nullptr) + this->pressure_sensor_->publish_state(pressure); + this->status_clear_warning(); + }); +} + +void EE895Component::write_command_(uint16_t addr, uint16_t reg_cnt) { + uint8_t address[7]; + uint16_t crc16 = 0; + address[0] = FUNCTION_CODE_READ; + address[1] = (addr >> 8) & 0xFF; + address[2] = addr & 0xFF; + address[3] = (reg_cnt >> 8) & 0xFF; + address[4] = reg_cnt & 0xFF; + crc16 = calc_crc16_(address, 6); + address[5] = crc16 & 0xFF; + address[6] = (crc16 >> 8) & 0xFF; + this->write(address, 7, true); +} + +float EE895Component::read_float_() { + uint16_t crc16_check = 0; + uint8_t i2c_response[8]; + this->read(i2c_response, 8); + crc16_check = (i2c_response[7] << 8) + i2c_response[6]; + if (crc16_check != calc_crc16_(i2c_response, 7)) { + this->error_code_ = CRC_CHECK_FAILED; + this->status_set_warning(); + return 0; + } + uint32_t x = encode_uint32(i2c_response[4], i2c_response[5], i2c_response[2], i2c_response[3]); + float value; + memcpy(&value, &x, sizeof(value)); // convert uin32_t IEEE-754 format to float + return value; +} + +uint16_t EE895Component::calc_crc16_(const uint8_t buf[], uint8_t len) { + uint8_t crc_check_buf[22]; + for (int i = 0; i < len; i++) { + crc_check_buf[i + 1] = buf[i]; + } + crc_check_buf[0] = this->address_; + return crc16(crc_check_buf, len); +} +} // namespace ee895 +} // namespace esphome diff --git a/esphome/components/ee895/ee895.h b/esphome/components/ee895/ee895.h new file mode 100644 index 0000000000..83bd7c6e82 --- /dev/null +++ b/esphome/components/ee895/ee895.h @@ -0,0 +1,34 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ee895 { + +/// This class implements support for the ee895 of temperature i2c sensors. +class EE895Component : public PollingComponent, public i2c::I2CDevice { + public: + void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; } + void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; } + void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; } + + float get_setup_priority() const override; + void setup() override; + void dump_config() override; + void update() override; + + protected: + void write_command_(uint16_t addr, uint16_t reg_cnt); + float read_float_(); + uint16_t calc_crc16_(const uint8_t buf[], uint8_t len); + sensor::Sensor *co2_sensor_; + sensor::Sensor *temperature_sensor_; + sensor::Sensor *pressure_sensor_; + + enum ErrorCode { NONE = 0, COMMUNICATION_FAILED, CRC_CHECK_FAILED } error_code_{NONE}; +}; + +} // namespace ee895 +} // namespace esphome diff --git a/esphome/components/ee895/sensor.py b/esphome/components/ee895/sensor.py new file mode 100644 index 0000000000..d06f9ca02f --- /dev/null +++ b/esphome/components/ee895/sensor.py @@ -0,0 +1,69 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import ( + CONF_ID, + CONF_PRESSURE, + CONF_TEMPERATURE, + CONF_CO2, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_PRESSURE, + STATE_CLASS_MEASUREMENT, + UNIT_HECTOPASCAL, + UNIT_CELSIUS, + ICON_MOLECULE_CO2, + UNIT_PARTS_PER_MILLION, +) + +CODEOWNERS = ["@Stock-M"] + +DEPENDENCIES = ["i2c"] + +ee895_ns = cg.esphome_ns.namespace("ee895") +EE895Component = ee895_ns.class_("EE895Component", cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(EE895Component), + cv.Required(CONF_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + accuracy_decimals=1, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_CO2): sensor.sensor_schema( + unit_of_measurement=UNIT_PARTS_PER_MILLION, + icon=ICON_MOLECULE_CO2, + accuracy_decimals=0, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Required(CONF_PRESSURE): sensor.sensor_schema( + unit_of_measurement=UNIT_HECTOPASCAL, + accuracy_decimals=1, + device_class=DEVICE_CLASS_PRESSURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + } + ) + .extend(cv.polling_component_schema("60s")) + .extend(i2c.i2c_device_schema(0x5F)) +) + + +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 CONF_TEMPERATURE in config: + sens = await sensor.new_sensor(config[CONF_TEMPERATURE]) + cg.add(var.set_temperature_sensor(sens)) + + if CONF_CO2 in config: + sens = await sensor.new_sensor(config[CONF_CO2]) + cg.add(var.set_co2_sensor(sens)) + + if CONF_PRESSURE in config: + sens = await sensor.new_sensor(config[CONF_PRESSURE]) + cg.add(var.set_pressure_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 53ef50d809..5caef0cafa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -568,6 +568,15 @@ sensor: - platform: duty_cycle pin: GPIO25 name: Duty Cycle Sensor + - platform: ee895 + co2: + name: Office CO2 1 + temperature: + name: Office Temperature 1 + pressure: + name: Office Pressure 1 + address: 0x5F + i2c_id: i2c_bus - platform: esp32_hall name: ESP32 Hall Sensor update_interval: 15s