From a919b015b40dbc10eef69ad0a17e00c68a1a0208 Mon Sep 17 00:00:00 2001 From: Sergio Date: Wed, 6 Nov 2019 13:59:00 +0100 Subject: [PATCH] Add support for INA226 Current/Power Monitor (#801) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add support for INA226 Current/Power Monitor * fix lint errors * fix narrowing conversion * Remove useless code Co-authored-by: Sergio Muñoz --- esphome/components/ina226/__init__.py | 0 esphome/components/ina226/ina226.cpp | 140 ++++++++++++++++++++++++++ esphome/components/ina226/ina226.h | 35 +++++++ esphome/components/ina226/sensor.py | 48 +++++++++ tests/test1.yaml | 13 +++ 5 files changed, 236 insertions(+) create mode 100644 esphome/components/ina226/__init__.py create mode 100644 esphome/components/ina226/ina226.cpp create mode 100644 esphome/components/ina226/ina226.h create mode 100644 esphome/components/ina226/sensor.py diff --git a/esphome/components/ina226/__init__.py b/esphome/components/ina226/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/ina226/ina226.cpp b/esphome/components/ina226/ina226.cpp new file mode 100644 index 0000000000..cbb06d73b6 --- /dev/null +++ b/esphome/components/ina226/ina226.cpp @@ -0,0 +1,140 @@ +#include "ina226.h" +#include "esphome/core/log.h" + +namespace esphome { +namespace ina226 { + +static const char *TAG = "ina226"; + +// | A0 | A1 | Address | +// | GND | GND | 0x40 | +// | GND | V_S+ | 0x41 | +// | GND | SDA | 0x42 | +// | GND | SCL | 0x43 | +// | V_S+ | GND | 0x44 | +// | V_S+ | V_S+ | 0x45 | +// | V_S+ | SDA | 0x46 | +// | V_S+ | SCL | 0x47 | +// | SDA | GND | 0x48 | +// | SDA | V_S+ | 0x49 | +// | SDA | SDA | 0x4A | +// | SDA | SCL | 0x4B | +// | SCL | GND | 0x4C | +// | SCL | V_S+ | 0x4D | +// | SCL | SDA | 0x4E | +// | SCL | SCL | 0x4F | + +static const uint8_t INA226_REGISTER_CONFIG = 0x00; +static const uint8_t INA226_REGISTER_SHUNT_VOLTAGE = 0x01; +static const uint8_t INA226_REGISTER_BUS_VOLTAGE = 0x02; +static const uint8_t INA226_REGISTER_POWER = 0x03; +static const uint8_t INA226_REGISTER_CURRENT = 0x04; +static const uint8_t INA226_REGISTER_CALIBRATION = 0x05; + +void INA226Component::setup() { + ESP_LOGCONFIG(TAG, "Setting up INA226..."); + // Config Register + // 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset) + if (!this->write_byte_16(INA226_REGISTER_CONFIG, 0x8000)) { + this->mark_failed(); + return; + } + delay(1); + + uint16_t config = 0x0000; + + // Averaging Mode AVG Bit Settings[11:9] (000 -> 1 sample, 001 -> 4 sample, 111 -> 1024 samples) + config |= 0b0000001000000000; + + // Bus Voltage Conversion Time VBUSCT Bit Settings [8:6] (100 -> 1.1ms, 111 -> 8.244 ms) + config |= 0b0000000100000000; + + // Shunt Voltage Conversion Time VSHCT Bit Settings [5:3] (100 -> 1.1ms, 111 -> 8.244 ms) + config |= 0b0000000000100000; + + // Mode Settings [2:0] Combinations (111 -> Shunt and Bus, Continuous) + config |= 0b0000000000000111; + + if (!this->write_byte_16(INA226_REGISTER_CONFIG, config)) { + this->mark_failed(); + return; + } + + // lsb is multiplied by 1000000 to store it as an integer value + uint32_t lsb = static_cast(ceilf(this->max_current_a_ * 1000000.0f / 32768)); + + this->calibration_lsb_ = lsb; + + auto calibration = uint32_t(0.00512 / (lsb * this->shunt_resistance_ohm_ / 1000000.0f)); + + ESP_LOGV(TAG, " Using LSB=%u calibration=%u", lsb, calibration); + + if (!this->write_byte_16(INA226_REGISTER_CALIBRATION, calibration)) { + this->mark_failed(); + return; + } +} + +void INA226Component::dump_config() { + ESP_LOGCONFIG(TAG, "INA226:"); + LOG_I2C_DEVICE(this); + + if (this->is_failed()) { + ESP_LOGE(TAG, "Communication with INA226 failed!"); + return; + } + LOG_UPDATE_INTERVAL(this); + + LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_); + LOG_SENSOR(" ", "Shunt Voltage", this->shunt_voltage_sensor_); + LOG_SENSOR(" ", "Current", this->current_sensor_); + LOG_SENSOR(" ", "Power", this->power_sensor_); +} + +float INA226Component::get_setup_priority() const { return setup_priority::DATA; } + +void INA226Component::update() { + if (this->bus_voltage_sensor_ != nullptr) { + uint16_t raw_bus_voltage; + if (!this->read_byte_16(INA226_REGISTER_BUS_VOLTAGE, &raw_bus_voltage, 1)) { + this->status_set_warning(); + return; + } + float bus_voltage_v = int16_t(raw_bus_voltage) * 0.00125f; + this->bus_voltage_sensor_->publish_state(bus_voltage_v); + } + + if (this->shunt_voltage_sensor_ != nullptr) { + uint16_t raw_shunt_voltage; + if (!this->read_byte_16(INA226_REGISTER_SHUNT_VOLTAGE, &raw_shunt_voltage, 1)) { + this->status_set_warning(); + } + float shunt_voltage_v = int16_t(raw_shunt_voltage) * 0.0000025f; + this->shunt_voltage_sensor_->publish_state(shunt_voltage_v); + } + + if (this->current_sensor_ != nullptr) { + uint16_t raw_current; + if (!this->read_byte_16(INA226_REGISTER_CURRENT, &raw_current, 1)) { + this->status_set_warning(); + return; + } + float current_ma = int16_t(raw_current) * (this->calibration_lsb_ / 1000.0f); + this->current_sensor_->publish_state(current_ma / 1000.0f); + } + + if (this->power_sensor_ != nullptr) { + uint16_t raw_power; + if (!this->read_byte_16(INA226_REGISTER_POWER, &raw_power, 1)) { + this->status_set_warning(); + return; + } + float power_mw = int16_t(raw_power) * (this->calibration_lsb_ * 25.0f / 1000.0f); + this->power_sensor_->publish_state(power_mw / 1000.0f); + } + + this->status_clear_warning(); +} + +} // namespace ina226 +} // namespace esphome diff --git a/esphome/components/ina226/ina226.h b/esphome/components/ina226/ina226.h new file mode 100644 index 0000000000..a551cb3430 --- /dev/null +++ b/esphome/components/ina226/ina226.h @@ -0,0 +1,35 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/i2c/i2c.h" + +namespace esphome { +namespace ina226 { + +class INA226Component : public PollingComponent, public i2c::I2CDevice { + public: + void setup() override; + void dump_config() override; + float get_setup_priority() const override; + void update() override; + + void set_shunt_resistance_ohm(float shunt_resistance_ohm) { shunt_resistance_ohm_ = shunt_resistance_ohm; } + void set_max_current_a(float max_current_a) { max_current_a_ = max_current_a; } + void set_bus_voltage_sensor(sensor::Sensor *bus_voltage_sensor) { bus_voltage_sensor_ = bus_voltage_sensor; } + void set_shunt_voltage_sensor(sensor::Sensor *shunt_voltage_sensor) { shunt_voltage_sensor_ = shunt_voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } + void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } + + protected: + float shunt_resistance_ohm_; + float max_current_a_; + uint32_t calibration_lsb_; + sensor::Sensor *bus_voltage_sensor_{nullptr}; + sensor::Sensor *shunt_voltage_sensor_{nullptr}; + sensor::Sensor *current_sensor_{nullptr}; + sensor::Sensor *power_sensor_{nullptr}; +}; + +} // namespace ina226 +} // namespace esphome diff --git a/esphome/components/ina226/sensor.py b/esphome/components/ina226/sensor.py new file mode 100644 index 0000000000..02a13f98c4 --- /dev/null +++ b/esphome/components/ina226/sensor.py @@ -0,0 +1,48 @@ +# coding=utf-8 +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import i2c, sensor +from esphome.const import CONF_BUS_VOLTAGE, CONF_CURRENT, CONF_ID, \ + CONF_MAX_CURRENT, CONF_POWER, CONF_SHUNT_RESISTANCE, \ + CONF_SHUNT_VOLTAGE, ICON_FLASH, UNIT_VOLT, UNIT_AMPERE, UNIT_WATT + +DEPENDENCIES = ['i2c'] + +ina226_ns = cg.esphome_ns.namespace('ina226') +INA226Component = ina226_ns.class_('INA226Component', cg.PollingComponent, i2c.I2CDevice) + +CONFIG_SCHEMA = cv.Schema({ + cv.GenerateID(): cv.declare_id(INA226Component), + cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), + cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(UNIT_VOLT, ICON_FLASH, 2), + cv.Optional(CONF_CURRENT): sensor.sensor_schema(UNIT_AMPERE, ICON_FLASH, 3), + cv.Optional(CONF_POWER): sensor.sensor_schema(UNIT_WATT, ICON_FLASH, 2), + cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(cv.resistance, cv.Range(min=0.0)), + cv.Optional(CONF_MAX_CURRENT, default=3.2): cv.All(cv.current, cv.Range(min=0.0)), +}).extend(cv.polling_component_schema('60s')).extend(i2c.i2c_device_schema(0x40)) + + +def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + yield cg.register_component(var, config) + yield i2c.register_i2c_device(var, config) + + cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE])) + + cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT])) + + if CONF_BUS_VOLTAGE in config: + sens = yield sensor.new_sensor(config[CONF_BUS_VOLTAGE]) + cg.add(var.set_bus_voltage_sensor(sens)) + + if CONF_SHUNT_VOLTAGE in config: + sens = yield sensor.new_sensor(config[CONF_SHUNT_VOLTAGE]) + cg.add(var.set_shunt_voltage_sensor(sens)) + + if CONF_CURRENT in config: + sens = yield sensor.new_sensor(config[CONF_CURRENT]) + cg.add(var.set_current_sensor(sens)) + + if CONF_POWER in config: + sens = yield sensor.new_sensor(config[CONF_POWER]) + cg.add(var.set_power_sensor(sens)) diff --git a/tests/test1.yaml b/tests/test1.yaml index 60f3b7f418..0905c51efa 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -424,6 +424,19 @@ sensor: max_voltage: 32.0V max_current: 3.2A update_interval: 15s + - platform: ina226 + address: 0x40 + shunt_resistance: 0.1 ohm + current: + name: "INA226 Current" + power: + name: "INA226 Power" + bus_voltage: + name: "INA226 Bus Voltage" + shunt_voltage: + name: "INA226 Shunt Voltage" + max_current: 3.2A + update_interval: 15s - platform: ina3221 address: 0x40 channel_1: