From cdb9c59662c42d5b51b3946d406ca58787beab37 Mon Sep 17 00:00:00 2001 From: Otto Winter Date: Wed, 16 Oct 2019 13:19:41 +0200 Subject: [PATCH] Add ADE7953 Support (#593) * Add ADE795 support * Lint * Fix * Fix, add test --- esphome/components/ade7953/__init__.py | 0 esphome/components/ade7953/ade7953.cpp | 51 ++++++++++++++++++++ esphome/components/ade7953/ade7953.h | 67 ++++++++++++++++++++++++++ esphome/components/ade7953/sensor.py | 39 +++++++++++++++ esphome/components/i2c/i2c.cpp | 8 +++ esphome/components/i2c/i2c.h | 43 ++++++++++++----- script/clang-tidy | 1 - tests/test3.yaml | 11 +++++ 8 files changed, 206 insertions(+), 14 deletions(-) create mode 100644 esphome/components/ade7953/__init__.py create mode 100644 esphome/components/ade7953/ade7953.cpp create mode 100644 esphome/components/ade7953/ade7953.h create mode 100644 esphome/components/ade7953/sensor.py diff --git a/esphome/components/ade7953/__init__.py b/esphome/components/ade7953/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ade7953/ade7953.cpp b/esphome/components/ade7953/ade7953.cpp new file mode 100644 index 0000000000..9316d9cad0 --- /dev/null +++ b/esphome/components/ade7953/ade7953.cpp @@ -0,0 +1,51 @@ +#include "ade7953.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ade7953 { + +static const char *TAG = "ade7953"; + +void ADE7953::dump_config() { + ESP_LOGCONFIG(TAG, "ADE7953:"); + LOG_I2C_DEVICE(this); + LOG_UPDATE_INTERVAL(this); + LOG_SENSOR(" ", "Voltage Sensor", this->voltage_sensor_); + LOG_SENSOR(" ", "Current A Sensor", this->current_a_sensor_); + LOG_SENSOR(" ", "Current B Sensor", this->current_b_sensor_); + LOG_SENSOR(" ", "Active Power A Sensor", this->active_power_a_sensor_); + LOG_SENSOR(" ", "Active Power B Sensor", this->active_power_b_sensor_); +} + +#define ADE_PUBLISH_(name, factor) \ + if (name) { \ + float value = *name / factor; \ + this->name##_sensor_->publish_state(value); \ + } +#define ADE_PUBLISH(name, factor) ADE_PUBLISH_(name, factor) + +void ADE7953::update() { + if (!this->is_setup_) + return; + + auto active_power_a = this->ade_read_(0x0312); + ADE_PUBLISH(active_power_a, 154.0f); + auto active_power_b = this->ade_read_(0x0313); + ADE_PUBLISH(active_power_b, 154.0f); + auto current_a = this->ade_read_(0x031A); + ADE_PUBLISH(current_a, 100000.0f); + auto current_b = this->ade_read_(0x031B); + ADE_PUBLISH(current_b, 100000.0f); + auto voltage = this->ade_read_(0x031C); + ADE_PUBLISH(voltage, 26000.0f); + + // auto apparent_power_a = this->ade_read_(0x0310); + // auto apparent_power_b = this->ade_read_(0x0311); + // auto reactive_power_a = this->ade_read_(0x0314); + // auto reactive_power_b = this->ade_read_(0x0315); + // auto power_factor_a = this->ade_read_(0x010A); + // auto power_factor_b = this->ade_read_(0x010B); +} + +} // namespace ade7953 +} // namespace esphome diff --git a/esphome/components/ade7953/ade7953.h b/esphome/components/ade7953/ade7953.h new file mode 100644 index 0000000000..7591bc1684 --- /dev/null +++ b/esphome/components/ade7953/ade7953.h @@ -0,0 +1,67 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/i2c/i2c.h" +#include "esphome/components/sensor/sensor.h" + +namespace esphome { +namespace ade7953 { + +class ADE7953 : public i2c::I2CDevice, public PollingComponent { + public: + void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } + void set_current_a_sensor(sensor::Sensor *current_a_sensor) { current_a_sensor_ = current_a_sensor; } + void set_current_b_sensor(sensor::Sensor *current_b_sensor) { current_b_sensor_ = current_b_sensor; } + void set_active_power_a_sensor(sensor::Sensor *active_power_a_sensor) { + active_power_a_sensor_ = active_power_a_sensor; + } + void set_active_power_b_sensor(sensor::Sensor *active_power_b_sensor) { + active_power_b_sensor_ = active_power_b_sensor; + } + + void setup() override { + this->set_timeout(100, [this]() { + this->ade_write_(0x0010, 0x04); + this->ade_write_(0x00FE, 0xAD); + this->ade_write_(0x0120, 0x0030); + this->is_setup_ = true; + }); + } + + void dump_config() override; + + void update() override; + + protected: + template bool ade_write_(uint16_t reg, T value) { + std::vector data; + data.push_back(reg >> 8); + data.push_back(reg >> 0); + for (int i = sizeof(T) - 1; i >= 0; i--) + data.push_back(value >> (i * 8)); + return this->write_bytes_raw(data); + } + template optional ade_read_(uint16_t reg) { + uint8_t hi = reg >> 8; + uint8_t lo = reg >> 0; + if (!this->write_bytes_raw({hi, lo})) + return {}; + auto ret = this->read_bytes_raw(); + if (!ret.has_value()) + return {}; + T result = 0; + for (int i = 0, j = sizeof(T) - 1; i < sizeof(T); i++, j--) + result |= T((*ret)[i]) << (j * 8); + return result; + } + + bool is_setup_{false}; + sensor::Sensor *voltage_sensor_{nullptr}; + sensor::Sensor *current_a_sensor_{nullptr}; + sensor::Sensor *current_b_sensor_{nullptr}; + sensor::Sensor *active_power_a_sensor_{nullptr}; + sensor::Sensor *active_power_b_sensor_{nullptr}; +}; + +} // namespace ade7953 +} // namespace esphome diff --git a/esphome/components/ade7953/sensor.py b/esphome/components/ade7953/sensor.py new file mode 100644 index 0000000000..4fcd307332 --- /dev/null +++ b/esphome/components/ade7953/sensor.py @@ -0,0 +1,39 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import sensor, i2c +from esphome.const import CONF_ID, CONF_VOLTAGE, \ + UNIT_VOLT, ICON_FLASH, UNIT_AMPERE, UNIT_WATT + +DEPENDENCIES = ['i2c'] + +ace7953_ns = cg.esphome_ns.namespace('ade7953') +ADE7953 = ace7953_ns.class_('ADE7953', cg.PollingComponent, i2c.I2CDevice) + +CONF_CURRENT_A = 'current_a' +CONF_CURRENT_B = 'current_b' +CONF_ACTIVE_POWER_A = 'active_power_a' +CONF_ACTIVE_POWER_B = 'active_power_b' + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(ADE7953), + + cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 1), + cv.Optional(CONF_CURRENT_A): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), + cv.Optional(CONF_CURRENT_B): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 2), + cv.Optional(CONF_ACTIVE_POWER_A): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), + cv.Optional(CONF_ACTIVE_POWER_B): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 1), +}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x38)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + for key in [CONF_VOLTAGE, CONF_CURRENT_A, CONF_CURRENT_B, CONF_ACTIVE_POWER_A, + CONF_ACTIVE_POWER_B]: + if key not in config: + continue + conf = config[key] + sens = yield sensor.new_sensor(conf) + cg.add(getattr(var, 'set_{}_sensor'.format(key))(sens)) diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index b93c7d6053..840944748c 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -135,6 +135,9 @@ bool I2CComponent::read_bytes(uint8_t address, uint8_t a_register, uint8_t *data delay(conversion); return this->raw_receive(address, data, len); } +bool I2CComponent::read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len) { + return this->raw_receive(address, data, len); +} bool I2CComponent::read_bytes_16(uint8_t address, uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion) { if (!this->write_bytes(address, a_register, nullptr, 0)) @@ -156,6 +159,11 @@ bool I2CComponent::write_bytes(uint8_t address, uint8_t a_register, const uint8_ this->raw_write(address, data, len); return this->raw_end_transmission(address); } +bool I2CComponent::write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len) { + this->raw_begin_transmission(address); + this->raw_write(address, data, len); + return this->raw_end_transmission(address); +} bool I2CComponent::write_bytes_16(uint8_t address, uint8_t a_register, const uint16_t *data, uint8_t len) { this->raw_begin_transmission(address); this->raw_write(address, &a_register, 1); diff --git a/esphome/components/i2c/i2c.h b/esphome/components/i2c/i2c.h index e41bd6c5e8..67cd0373d3 100644 --- a/esphome/components/i2c/i2c.h +++ b/esphome/components/i2c/i2c.h @@ -42,6 +42,7 @@ class I2CComponent : public Component { * @return If the operation was successful. */ bool read_bytes(uint8_t address, uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); + bool read_bytes_raw(uint8_t address, uint8_t *data, uint8_t len); /** Read len amount of 16-bit words (MSB first) from a register into data. * @@ -69,6 +70,7 @@ class I2CComponent : public Component { * @return If the operation was successful. */ bool write_bytes(uint8_t address, uint8_t a_register, const uint8_t *data, uint8_t len); + bool write_bytes_raw(uint8_t address, const uint8_t *data, uint8_t len); /** Write len amount of 16-bit words (MSB first) to the specified register for address. * @@ -151,7 +153,6 @@ class I2CDevice { /// Manually set the parent i2c bus for this device. void set_i2c_parent(I2CComponent *parent); - protected: /** Read len amount of bytes from a register into data. Optionally with a conversion time after * writing the register value to the bus. * @@ -161,15 +162,23 @@ class I2CDevice { * @param conversion The time in ms between writing the register value and reading out the value. * @return If the operation was successful. */ - bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT + bool read_bytes(uint8_t a_register, uint8_t *data, uint8_t len, uint32_t conversion = 0); + bool read_bytes_raw(uint8_t *data, uint8_t len) { return this->parent_->read_bytes_raw(this->address_, data, len); } - template optional> read_bytes(uint8_t a_register) { // NOLINT + template optional> read_bytes(uint8_t a_register) { std::array res; if (!this->read_bytes(a_register, res.data(), N)) { return {}; } return res; } + template optional> read_bytes_raw() { + std::array res; + if (!this->read_bytes_raw(res.data(), N)) { + return {}; + } + return res; + } /** Read len amount of 16-bit words (MSB first) from a register into data. * @@ -179,12 +188,12 @@ class I2CDevice { * @param conversion The time in ms between writing the register value and reading out the value. * @return If the operation was successful. */ - bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); // NOLINT + bool read_bytes_16(uint8_t a_register, uint16_t *data, uint8_t len, uint32_t conversion = 0); /// Read a single byte from a register into the data variable. Return true if successful. - bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); // NOLINT + bool read_byte(uint8_t a_register, uint8_t *data, uint32_t conversion = 0); - optional read_byte(uint8_t a_register) { // NOLINT + optional read_byte(uint8_t a_register) { uint8_t data; if (!this->read_byte(a_register, &data)) return {}; @@ -192,7 +201,7 @@ class I2CDevice { } /// Read a single 16-bit words (MSB first) from a register into the data variable. Return true if successful. - bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); // NOLINT + bool read_byte_16(uint8_t a_register, uint16_t *data, uint32_t conversion = 0); /** Write len amount of 8-bit bytes to the specified register. * @@ -201,7 +210,10 @@ class I2CDevice { * @param len The amount of bytes to write to the bus. * @return If the operation was successful. */ - bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); // NOLINT + bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len); + bool write_bytes_raw(const uint8_t *data, uint8_t len) { + return this->parent_->write_bytes_raw(this->address_, data, len); + } /** Write a vector of data to a register. * @@ -209,13 +221,17 @@ class I2CDevice { * @param data The data to write. * @return If the operation was successful. */ - bool write_bytes(uint8_t a_register, const std::vector &data) { // NOLINT + bool write_bytes(uint8_t a_register, const std::vector &data) { return this->write_bytes(a_register, data.data(), data.size()); } + bool write_bytes_raw(const std::vector &data) { return this->write_bytes_raw(data.data(), data.size()); } - template bool write_bytes(uint8_t a_register, const std::array &data) { // NOLINT + template bool write_bytes(uint8_t a_register, const std::array &data) { return this->write_bytes(a_register, data.data(), data.size()); } + template bool write_bytes_raw(const std::array &data) { + return this->write_bytes_raw(data.data(), data.size()); + } /** Write len amount of 16-bit words (MSB first) to the specified register. * @@ -224,14 +240,15 @@ class I2CDevice { * @param len The amount of bytes to write to the bus. * @return If the operation was successful. */ - bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); // NOLINT + bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len); /// Write a single byte of data into the specified register. Return true if successful. - bool write_byte(uint8_t a_register, uint8_t data); // NOLINT + bool write_byte(uint8_t a_register, uint8_t data); /// Write a single 16-bit word of data into the specified register. Return true if successful. - bool write_byte_16(uint8_t a_register, uint16_t data); // NOLINT + bool write_byte_16(uint8_t a_register, uint16_t data); + protected: uint8_t address_{0x00}; I2CComponent *parent_{nullptr}; }; diff --git a/script/clang-tidy b/script/clang-tidy index 39df87df22..f178e036b1 100755 --- a/script/clang-tidy +++ b/script/clang-tidy @@ -54,7 +54,6 @@ def run_tidy(args, tmpdir, queue, lock, failed_files): if rc != 0: print() print("\033[0;32m************* File \033[1;32m{}\033[0m".format(path)) - print(invocation_s) print(output) print() failed_files.append(path) diff --git a/tests/test3.yaml b/tests/test3.yaml index 458021d0d3..c572b5efc5 100644 --- a/tests/test3.yaml +++ b/tests/test3.yaml @@ -203,6 +203,17 @@ sensor: value: 15.0 - binary_sensor: bin3 value: 100.0 + - platform: ade7953 + voltage: + name: ADE7953 Voltage + current_a: + name: ADE7953 Current A + current_b: + name: ADE7953 Current B + active_power_a: + name: ADE7953 Active Power A + active_power_b: + name: ADE7953 Active Power B time: - platform: homeassistant