mirror of
https://github.com/esphome/esphome.git
synced 2024-11-27 17:27:59 +01:00
INA228/INA229, INA238/INA239, INA237 power/energy/charge monitor (I2C, SPI) (#6138)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
parent
034c196ad8
commit
b06e0746f5
26 changed files with 1415 additions and 0 deletions
|
@ -180,6 +180,9 @@ esphome/components/improv_base/* @esphome/core
|
||||||
esphome/components/improv_serial/* @esphome/core
|
esphome/components/improv_serial/* @esphome/core
|
||||||
esphome/components/ina226/* @Sergio303 @latonita
|
esphome/components/ina226/* @Sergio303 @latonita
|
||||||
esphome/components/ina260/* @mreditor97
|
esphome/components/ina260/* @mreditor97
|
||||||
|
esphome/components/ina2xx_base/* @latonita
|
||||||
|
esphome/components/ina2xx_i2c/* @latonita
|
||||||
|
esphome/components/ina2xx_spi/* @latonita
|
||||||
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
esphome/components/inkbird_ibsth1_mini/* @fkirill
|
||||||
esphome/components/inkplate6/* @jesserockz
|
esphome/components/inkplate6/* @jesserockz
|
||||||
esphome/components/integration/* @OttoWinter
|
esphome/components/integration/* @OttoWinter
|
||||||
|
|
255
esphome/components/ina2xx_base/__init__.py
Normal file
255
esphome/components/ina2xx_base/__init__.py
Normal file
|
@ -0,0 +1,255 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import sensor
|
||||||
|
from esphome.const import (
|
||||||
|
CONF_BUS_VOLTAGE,
|
||||||
|
CONF_CURRENT,
|
||||||
|
CONF_ENERGY,
|
||||||
|
CONF_MAX_CURRENT,
|
||||||
|
CONF_MODEL,
|
||||||
|
CONF_NAME,
|
||||||
|
CONF_POWER,
|
||||||
|
CONF_SHUNT_RESISTANCE,
|
||||||
|
CONF_SHUNT_VOLTAGE,
|
||||||
|
CONF_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_CURRENT,
|
||||||
|
DEVICE_CLASS_ENERGY,
|
||||||
|
DEVICE_CLASS_POWER,
|
||||||
|
DEVICE_CLASS_TEMPERATURE,
|
||||||
|
DEVICE_CLASS_VOLTAGE,
|
||||||
|
STATE_CLASS_MEASUREMENT,
|
||||||
|
UNIT_AMPERE,
|
||||||
|
UNIT_CELSIUS,
|
||||||
|
UNIT_VOLT,
|
||||||
|
UNIT_WATT_HOURS,
|
||||||
|
UNIT_WATT,
|
||||||
|
)
|
||||||
|
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
|
||||||
|
CONF_ADC_AVERAGING = "adc_averaging"
|
||||||
|
CONF_ADC_RANGE = "adc_range"
|
||||||
|
CONF_ADC_TIME = "adc_time"
|
||||||
|
CONF_CHARGE = "charge"
|
||||||
|
CONF_CHARGE_COULOMBS = "charge_coulombs"
|
||||||
|
CONF_ENERGY_JOULES = "energy_joules"
|
||||||
|
CONF_TEMPERATURE_COEFFICIENT = "temperature_coefficient"
|
||||||
|
UNIT_AMPERE_HOURS = "Ah"
|
||||||
|
UNIT_COULOMB = "C"
|
||||||
|
UNIT_JOULE = "J"
|
||||||
|
UNIT_MILLIVOLT = "mV"
|
||||||
|
|
||||||
|
ina2xx_base_ns = cg.esphome_ns.namespace("ina2xx_base")
|
||||||
|
INA2XX = ina2xx_base_ns.class_("INA2XX", cg.PollingComponent)
|
||||||
|
|
||||||
|
AdcTime = ina2xx_base_ns.enum("AdcTime")
|
||||||
|
ADC_TIMES = {
|
||||||
|
50: AdcTime.ADC_TIME_50US,
|
||||||
|
84: AdcTime.ADC_TIME_84US,
|
||||||
|
150: AdcTime.ADC_TIME_150US,
|
||||||
|
280: AdcTime.ADC_TIME_280US,
|
||||||
|
540: AdcTime.ADC_TIME_540US,
|
||||||
|
1052: AdcTime.ADC_TIME_1052US,
|
||||||
|
2074: AdcTime.ADC_TIME_2074US,
|
||||||
|
4120: AdcTime.ADC_TIME_4120US,
|
||||||
|
}
|
||||||
|
|
||||||
|
AdcAvgSamples = ina2xx_base_ns.enum("AdcAvgSamples")
|
||||||
|
ADC_SAMPLES = {
|
||||||
|
1: AdcAvgSamples.ADC_AVG_SAMPLES_1,
|
||||||
|
4: AdcAvgSamples.ADC_AVG_SAMPLES_4,
|
||||||
|
16: AdcAvgSamples.ADC_AVG_SAMPLES_16,
|
||||||
|
64: AdcAvgSamples.ADC_AVG_SAMPLES_64,
|
||||||
|
128: AdcAvgSamples.ADC_AVG_SAMPLES_128,
|
||||||
|
256: AdcAvgSamples.ADC_AVG_SAMPLES_256,
|
||||||
|
512: AdcAvgSamples.ADC_AVG_SAMPLES_512,
|
||||||
|
1024: AdcAvgSamples.ADC_AVG_SAMPLES_1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
SENSOR_MODEL_OPTIONS = {
|
||||||
|
CONF_ENERGY: ["INA228", "INA229"],
|
||||||
|
CONF_ENERGY_JOULES: ["INA228", "INA229"],
|
||||||
|
CONF_CHARGE: ["INA228", "INA229"],
|
||||||
|
CONF_CHARGE_COULOMBS: ["INA228", "INA229"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def validate_model_config(config):
|
||||||
|
model = config[CONF_MODEL]
|
||||||
|
|
||||||
|
for key in config:
|
||||||
|
if key in SENSOR_MODEL_OPTIONS:
|
||||||
|
if model not in SENSOR_MODEL_OPTIONS[key]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Device model '{model}' does not support '{key}' sensor"
|
||||||
|
)
|
||||||
|
|
||||||
|
tempco = config[CONF_TEMPERATURE_COEFFICIENT]
|
||||||
|
if tempco > 0 and model not in ["INA228", "INA229"]:
|
||||||
|
raise cv.Invalid(
|
||||||
|
f"Device model '{model}' does not support temperature coefficient"
|
||||||
|
)
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
|
||||||
|
def validate_adc_time(value):
|
||||||
|
value = cv.positive_time_period_microseconds(value).total_microseconds
|
||||||
|
return cv.enum(ADC_TIMES, int=True)(value)
|
||||||
|
|
||||||
|
|
||||||
|
INA2XX_SCHEMA = cv.Schema(
|
||||||
|
{
|
||||||
|
cv.Required(CONF_SHUNT_RESISTANCE): cv.All(cv.resistance, cv.Range(min=0.0)),
|
||||||
|
cv.Required(CONF_MAX_CURRENT): cv.All(cv.current, cv.Range(min=0.0)),
|
||||||
|
cv.Optional(CONF_ADC_RANGE, default=0): cv.int_range(min=0, max=1),
|
||||||
|
cv.Optional(CONF_ADC_TIME, default="4120 us"): cv.Any(
|
||||||
|
validate_adc_time,
|
||||||
|
{
|
||||||
|
cv.Optional(CONF_BUS_VOLTAGE, default="4120 us"): validate_adc_time,
|
||||||
|
cv.Optional(CONF_SHUNT_VOLTAGE, default="4120 us"): validate_adc_time,
|
||||||
|
cv.Optional(CONF_TEMPERATURE, default="4120 us"): validate_adc_time,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ADC_AVERAGING, default=128): cv.enum(ADC_SAMPLES, int=True),
|
||||||
|
cv.Optional(CONF_TEMPERATURE_COEFFICIENT, default=0): cv.int_range(
|
||||||
|
min=0, max=16383
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_SHUNT_VOLTAGE): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_MILLIVOLT,
|
||||||
|
accuracy_decimals=5,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_BUS_VOLTAGE): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_VOLT,
|
||||||
|
accuracy_decimals=5,
|
||||||
|
device_class=DEVICE_CLASS_VOLTAGE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_TEMPERATURE): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_CELSIUS,
|
||||||
|
accuracy_decimals=5,
|
||||||
|
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CURRENT): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE,
|
||||||
|
accuracy_decimals=8,
|
||||||
|
device_class=DEVICE_CLASS_CURRENT,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_POWER): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_WATT,
|
||||||
|
accuracy_decimals=6,
|
||||||
|
device_class=DEVICE_CLASS_POWER,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ENERGY): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_WATT_HOURS,
|
||||||
|
accuracy_decimals=8,
|
||||||
|
device_class=DEVICE_CLASS_ENERGY,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_ENERGY_JOULES): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_JOULE,
|
||||||
|
accuracy_decimals=8,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CHARGE): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_AMPERE_HOURS,
|
||||||
|
accuracy_decimals=8,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
cv.Optional(CONF_CHARGE_COULOMBS): cv.maybe_simple_value(
|
||||||
|
sensor.sensor_schema(
|
||||||
|
unit_of_measurement=UNIT_COULOMB,
|
||||||
|
accuracy_decimals=8,
|
||||||
|
state_class=STATE_CLASS_MEASUREMENT,
|
||||||
|
),
|
||||||
|
key=CONF_NAME,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
).extend(cv.polling_component_schema("60s"))
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_ina2xx(var, config):
|
||||||
|
await cg.register_component(var, config)
|
||||||
|
|
||||||
|
cg.add(var.set_model(config[CONF_MODEL]))
|
||||||
|
|
||||||
|
cg.add(var.set_shunt_resistance_ohm(config[CONF_SHUNT_RESISTANCE]))
|
||||||
|
cg.add(var.set_max_current_a(config[CONF_MAX_CURRENT]))
|
||||||
|
cg.add(var.set_adc_range(config[CONF_ADC_RANGE]))
|
||||||
|
cg.add(var.set_adc_avg_samples(config[CONF_ADC_AVERAGING]))
|
||||||
|
cg.add(var.set_shunt_tempco(config[CONF_TEMPERATURE_COEFFICIENT]))
|
||||||
|
|
||||||
|
adc_time_config = config[CONF_ADC_TIME]
|
||||||
|
if isinstance(adc_time_config, dict):
|
||||||
|
cg.add(var.set_adc_time_bus_voltage(adc_time_config[CONF_BUS_VOLTAGE]))
|
||||||
|
cg.add(var.set_adc_time_shunt_voltage(adc_time_config[CONF_SHUNT_VOLTAGE]))
|
||||||
|
cg.add(var.set_adc_time_die_temperature(adc_time_config[CONF_TEMPERATURE]))
|
||||||
|
else:
|
||||||
|
cg.add(var.set_adc_time_bus_voltage(adc_time_config))
|
||||||
|
cg.add(var.set_adc_time_shunt_voltage(adc_time_config))
|
||||||
|
cg.add(var.set_adc_time_die_temperature(adc_time_config))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_SHUNT_VOLTAGE):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_shunt_voltage_sensor(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_BUS_VOLTAGE):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_bus_voltage_sensor(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_TEMPERATURE):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_die_temperature_sensor(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_CURRENT):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_current_sensor(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_POWER):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_power_sensor(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_ENERGY):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_energy_sensor_wh(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_ENERGY_JOULES):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_energy_sensor_j(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_CHARGE):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_charge_sensor_ah(sens))
|
||||||
|
|
||||||
|
if conf := config.get(CONF_CHARGE_COULOMBS):
|
||||||
|
sens = await sensor.new_sensor(conf)
|
||||||
|
cg.add(var.set_charge_sensor_c(sens))
|
604
esphome/components/ina2xx_base/ina2xx_base.cpp
Normal file
604
esphome/components/ina2xx_base/ina2xx_base.cpp
Normal file
|
@ -0,0 +1,604 @@
|
||||||
|
#include "ina2xx_base.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include <cinttypes>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_base {
|
||||||
|
|
||||||
|
static const char *const TAG = "ina2xx";
|
||||||
|
|
||||||
|
#define OKFAILED(b) ((b) ? "OK" : "FAILED")
|
||||||
|
|
||||||
|
static const uint16_t ADC_TIMES[8] = {50, 84, 150, 280, 540, 1052, 2074, 4120};
|
||||||
|
static const uint16_t ADC_SAMPLES[8] = {1, 4, 16, 64, 128, 256, 512, 1024};
|
||||||
|
|
||||||
|
static const char *get_device_name(INAModel model) {
|
||||||
|
switch (model) {
|
||||||
|
case INAModel::INA_228:
|
||||||
|
return "INA228";
|
||||||
|
case INAModel::INA_229:
|
||||||
|
return "INA229";
|
||||||
|
case INAModel::INA_238:
|
||||||
|
return "INA238";
|
||||||
|
case INAModel::INA_239:
|
||||||
|
return "INA239";
|
||||||
|
case INAModel::INA_237:
|
||||||
|
return "INA237";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool check_model_and_device_match(INAModel model, uint16_t dev_id) {
|
||||||
|
switch (model) {
|
||||||
|
case INAModel::INA_228:
|
||||||
|
return dev_id == 0x228;
|
||||||
|
case INAModel::INA_229:
|
||||||
|
return dev_id == 0x229;
|
||||||
|
case INAModel::INA_238:
|
||||||
|
return dev_id == 0x238;
|
||||||
|
case INAModel::INA_239:
|
||||||
|
return dev_id == 0x239;
|
||||||
|
case INAModel::INA_237:
|
||||||
|
return dev_id == 0x237;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA2XX::setup() {
|
||||||
|
ESP_LOGCONFIG(TAG, "Setting up INA2xx...");
|
||||||
|
|
||||||
|
if (!this->reset_config_()) {
|
||||||
|
ESP_LOGE(TAG, "Reset failed, check connection");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delay(2);
|
||||||
|
|
||||||
|
if (!this->check_device_model_()) {
|
||||||
|
ESP_LOGE(TAG, "Device not supported or model selected improperly in yaml file");
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
this->configure_adc_range_();
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
this->configure_adc_();
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
this->configure_shunt_();
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
this->configure_shunt_tempco_();
|
||||||
|
delay(1);
|
||||||
|
|
||||||
|
this->state_ = State::IDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
float INA2XX::get_setup_priority() const { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void INA2XX::update() {
|
||||||
|
ESP_LOGD(TAG, "Updating");
|
||||||
|
if (this->is_ready() && this->state_ == State::IDLE) {
|
||||||
|
ESP_LOGD(TAG, "Initiating new data collection");
|
||||||
|
this->state_ = State::DATA_COLLECTION_1;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA2XX::loop() {
|
||||||
|
if (this->is_ready()) {
|
||||||
|
switch (this->state_) {
|
||||||
|
case State::NOT_INITIALIZED:
|
||||||
|
case State::IDLE:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_1:
|
||||||
|
this->full_loop_is_okay_ = true;
|
||||||
|
|
||||||
|
if (this->shunt_voltage_sensor_ != nullptr) {
|
||||||
|
float shunt_voltage{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_shunt_voltage_mv_(shunt_voltage);
|
||||||
|
this->shunt_voltage_sensor_->publish_state(shunt_voltage);
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_2:
|
||||||
|
if (this->bus_voltage_sensor_ != nullptr) {
|
||||||
|
float bus_voltage{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_bus_voltage_(bus_voltage);
|
||||||
|
this->bus_voltage_sensor_->publish_state(bus_voltage);
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_3;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_3:
|
||||||
|
if (this->die_temperature_sensor_ != nullptr) {
|
||||||
|
float die_temperature{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_die_temp_c_(die_temperature);
|
||||||
|
this->die_temperature_sensor_->publish_state(die_temperature);
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_4;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_4:
|
||||||
|
if (this->current_sensor_ != nullptr) {
|
||||||
|
float current{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_current_a_(current);
|
||||||
|
this->current_sensor_->publish_state(current);
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_5;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_5:
|
||||||
|
if (this->power_sensor_ != nullptr) {
|
||||||
|
float power{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_power_w_(power);
|
||||||
|
this->power_sensor_->publish_state(power);
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_6;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_6:
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
if (this->energy_sensor_j_ != nullptr || this->energy_sensor_wh_ != nullptr ||
|
||||||
|
this->charge_sensor_c_ != nullptr || this->charge_sensor_ah_ != nullptr) {
|
||||||
|
this->read_diagnostics_and_act_();
|
||||||
|
}
|
||||||
|
if (this->energy_sensor_j_ != nullptr || this->energy_sensor_wh_ != nullptr) {
|
||||||
|
double energy_j{0}, energy_wh{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_energy_(energy_j, energy_wh);
|
||||||
|
if (this->energy_sensor_j_ != nullptr)
|
||||||
|
this->energy_sensor_j_->publish_state(energy_j);
|
||||||
|
if (this->energy_sensor_wh_ != nullptr)
|
||||||
|
this->energy_sensor_wh_->publish_state(energy_wh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_7;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_7:
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
if (this->charge_sensor_c_ != nullptr || this->charge_sensor_ah_ != nullptr) {
|
||||||
|
double charge_c{0}, charge_ah{0};
|
||||||
|
this->full_loop_is_okay_ &= this->read_charge_(charge_c, charge_ah);
|
||||||
|
if (this->charge_sensor_c_ != nullptr)
|
||||||
|
this->charge_sensor_c_->publish_state(charge_c);
|
||||||
|
if (this->charge_sensor_ah_ != nullptr)
|
||||||
|
this->charge_sensor_ah_->publish_state(charge_ah);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->state_ = State::DATA_COLLECTION_8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case State::DATA_COLLECTION_8:
|
||||||
|
if (this->full_loop_is_okay_) {
|
||||||
|
this->status_clear_warning();
|
||||||
|
} else {
|
||||||
|
this->status_set_warning();
|
||||||
|
}
|
||||||
|
this->state_ = State::IDLE;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ESP_LOGW(TAG, "Unknown state of the component, might be due to memory corruption");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA2XX::dump_config() {
|
||||||
|
ESP_LOGCONFIG(TAG, "INA2xx:");
|
||||||
|
ESP_LOGCONFIG(TAG, " Device model = %s", get_device_name(this->ina_model_));
|
||||||
|
|
||||||
|
if (this->device_mismatch_) {
|
||||||
|
ESP_LOGE(TAG, " Device model mismatch. Found device with ID = %x. Please check your configuration.",
|
||||||
|
this->dev_id_);
|
||||||
|
}
|
||||||
|
if (this->is_failed()) {
|
||||||
|
ESP_LOGE(TAG, "Communication with INA2xx failed!");
|
||||||
|
}
|
||||||
|
LOG_UPDATE_INTERVAL(this);
|
||||||
|
ESP_LOGCONFIG(TAG, " Shunt resistance = %f Ohm", this->shunt_resistance_ohm_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Max current = %f A", this->max_current_a_);
|
||||||
|
ESP_LOGCONFIG(TAG, " Shunt temp coeff = %d ppm/°C", this->shunt_tempco_ppm_c_);
|
||||||
|
ESP_LOGCONFIG(TAG, " ADCRANGE = %d (%s)", (uint8_t) this->adc_range_, this->adc_range_ ? "±40.96 mV" : "±163.84 mV");
|
||||||
|
ESP_LOGCONFIG(TAG, " CURRENT_LSB = %f", this->current_lsb_);
|
||||||
|
ESP_LOGCONFIG(TAG, " SHUNT_CAL = %d", this->shunt_cal_);
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, " ADC Samples = %d; ADC times: Bus = %d μs, Shunt = %d μs, Temp = %d μs",
|
||||||
|
ADC_SAMPLES[0b111 & (uint8_t) this->adc_avg_samples_],
|
||||||
|
ADC_TIMES[0b111 & (uint8_t) this->adc_time_bus_voltage_],
|
||||||
|
ADC_TIMES[0b111 & (uint8_t) this->adc_time_shunt_voltage_],
|
||||||
|
ADC_TIMES[0b111 & (uint8_t) this->adc_time_die_temperature_]);
|
||||||
|
|
||||||
|
ESP_LOGCONFIG(TAG, " Device is %s", get_device_name(this->ina_model_));
|
||||||
|
|
||||||
|
LOG_SENSOR(" ", "Shunt Voltage", this->shunt_voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Bus Voltage", this->bus_voltage_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Die Temperature", this->die_temperature_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Current", this->current_sensor_);
|
||||||
|
LOG_SENSOR(" ", "Power", this->power_sensor_);
|
||||||
|
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
LOG_SENSOR(" ", "Energy J", this->energy_sensor_j_);
|
||||||
|
LOG_SENSOR(" ", "Energy Wh", this->energy_sensor_wh_);
|
||||||
|
LOG_SENSOR(" ", "Charge C", this->charge_sensor_c_);
|
||||||
|
LOG_SENSOR(" ", "Charge Ah", this->charge_sensor_ah_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::reset_energy_counters() {
|
||||||
|
if (this->ina_model_ != INAModel::INA_228 && this->ina_model_ != INAModel::INA_229) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "reset_energy_counters");
|
||||||
|
|
||||||
|
ConfigurationRegister cfg{0};
|
||||||
|
auto ret = this->read_unsigned_16_(RegisterMap::REG_CONFIG, cfg.raw_u16);
|
||||||
|
cfg.RSTACC = true;
|
||||||
|
cfg.ADCRANGE = this->adc_range_;
|
||||||
|
ret = ret && this->write_unsigned_16_(RegisterMap::REG_CONFIG, cfg.raw_u16);
|
||||||
|
|
||||||
|
this->energy_overflows_count_ = 0;
|
||||||
|
this->charge_overflows_count_ = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::reset_config_() {
|
||||||
|
ESP_LOGV(TAG, "Reset");
|
||||||
|
ConfigurationRegister cfg{0};
|
||||||
|
cfg.RST = true;
|
||||||
|
return this->write_unsigned_16_(RegisterMap::REG_CONFIG, cfg.raw_u16);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::check_device_model_() {
|
||||||
|
constexpr uint16_t manufacturer_ti = 0x5449; // "TI"
|
||||||
|
|
||||||
|
uint16_t manufacturer_id{0}, rev_id{0};
|
||||||
|
this->read_unsigned_16_(RegisterMap::REG_MANUFACTURER_ID, manufacturer_id);
|
||||||
|
if (!this->read_unsigned_16_(RegisterMap::REG_DEVICE_ID, this->dev_id_)) {
|
||||||
|
this->dev_id_ = 0;
|
||||||
|
ESP_LOGV(TAG, "Can't read device ID");
|
||||||
|
};
|
||||||
|
rev_id = this->dev_id_ & 0x0F;
|
||||||
|
this->dev_id_ >>= 4;
|
||||||
|
ESP_LOGI(TAG, "Manufacturer: 0x%04X, Device ID: 0x%04X, Revision: %d", manufacturer_id, this->dev_id_, rev_id);
|
||||||
|
|
||||||
|
if (manufacturer_id != manufacturer_ti) {
|
||||||
|
ESP_LOGE(TAG, "Manufacturer ID doesn't match original 0x5449");
|
||||||
|
this->device_mismatch_ = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->dev_id_ == 0x228 || this->dev_id_ == 0x229) {
|
||||||
|
ESP_LOGI(TAG, "Supported device found: INA%x, 85-V, 20-Bit, Ultra-Precise Power/Energy/Charge Monitor",
|
||||||
|
this->dev_id_);
|
||||||
|
} else if (this->dev_id_ == 0x238 || this->dev_id_ == 0x239) {
|
||||||
|
ESP_LOGI(TAG, "Supported device found: INA%x, 85-V, 16-Bit, High-Precision Power Monitor", this->dev_id_);
|
||||||
|
} else if (this->dev_id_ == 0x0 || this->dev_id_ == 0xFF) {
|
||||||
|
ESP_LOGI(TAG, "We assume device is: INA237 85-V, 16-Bit, Precision Power Monitor");
|
||||||
|
this->dev_id_ = 0x237;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Unknown device ID %x.", this->dev_id_);
|
||||||
|
this->device_mismatch_ = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user-selected model agains what we have found. Mark as failed if selected model != found model
|
||||||
|
if (!check_model_and_device_match(this->ina_model_, this->dev_id_)) {
|
||||||
|
ESP_LOGE(TAG, "Selected model %s doesn't match found device INA%x", get_device_name(this->ina_model_),
|
||||||
|
this->dev_id_);
|
||||||
|
this->device_mismatch_ = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup device coefficients
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
this->cfg_.vbus_lsb = 0.0001953125f;
|
||||||
|
this->cfg_.v_shunt_lsb_range0 = 0.0003125f;
|
||||||
|
this->cfg_.v_shunt_lsb_range1 = 0.000078125f;
|
||||||
|
this->cfg_.shunt_cal_scale = 13107.2f * 1000000.0f;
|
||||||
|
this->cfg_.current_lsb_scale_factor = -19;
|
||||||
|
this->cfg_.die_temp_lsb = 0.0078125f;
|
||||||
|
this->cfg_.power_coeff = 3.2f;
|
||||||
|
this->cfg_.energy_coeff = 16.0f * 3.2f;
|
||||||
|
} else {
|
||||||
|
this->cfg_.vbus_lsb = 0.0031250000f;
|
||||||
|
this->cfg_.v_shunt_lsb_range0 = 0.0050000f;
|
||||||
|
this->cfg_.v_shunt_lsb_range1 = 0.001250000f;
|
||||||
|
this->cfg_.shunt_cal_scale = 819.2f * 1000000.0f;
|
||||||
|
this->cfg_.current_lsb_scale_factor = -15;
|
||||||
|
this->cfg_.die_temp_lsb = 0.1250000f;
|
||||||
|
this->cfg_.power_coeff = 0.2f;
|
||||||
|
this->cfg_.energy_coeff = 0.0f; // N/A
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::configure_adc_range_() {
|
||||||
|
ESP_LOGV(TAG, "Setting ADCRANGE = %d", (uint8_t) this->adc_range_);
|
||||||
|
ConfigurationRegister cfg{0};
|
||||||
|
auto ret = this->read_unsigned_16_(RegisterMap::REG_CONFIG, cfg.raw_u16);
|
||||||
|
cfg.ADCRANGE = this->adc_range_;
|
||||||
|
ret = ret && this->write_unsigned_16_(RegisterMap::REG_CONFIG, cfg.raw_u16);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::configure_adc_() {
|
||||||
|
bool ret{false};
|
||||||
|
AdcConfigurationRegister adc_cfg{0};
|
||||||
|
adc_cfg.MODE = 0x0F; // Fh = Continuous bus voltage, shunt voltage and temperature
|
||||||
|
adc_cfg.VBUSCT = this->adc_time_bus_voltage_;
|
||||||
|
adc_cfg.VSHCT = this->adc_time_shunt_voltage_;
|
||||||
|
adc_cfg.VTCT = this->adc_time_die_temperature_;
|
||||||
|
adc_cfg.AVG = this->adc_avg_samples_;
|
||||||
|
ret = this->write_unsigned_16_(RegisterMap::REG_ADC_CONFIG, adc_cfg.raw_u16);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::configure_shunt_() {
|
||||||
|
this->current_lsb_ = ldexp(this->max_current_a_, this->cfg_.current_lsb_scale_factor);
|
||||||
|
this->shunt_cal_ = (uint16_t) (this->cfg_.shunt_cal_scale * this->current_lsb_ * this->shunt_resistance_ohm_);
|
||||||
|
if (this->adc_range_)
|
||||||
|
this->shunt_cal_ *= 4;
|
||||||
|
|
||||||
|
if (this->shunt_cal_ & 0x8000) {
|
||||||
|
// cant be more than 15 bits
|
||||||
|
ESP_LOGW(TAG, "Shunt value too high");
|
||||||
|
}
|
||||||
|
this->shunt_cal_ &= 0x7FFF;
|
||||||
|
ESP_LOGV(TAG, "Given Rshunt=%f Ohm and Max_current=%.3f", this->shunt_resistance_ohm_, this->max_current_a_);
|
||||||
|
ESP_LOGV(TAG, "New CURRENT_LSB=%f, SHUNT_CAL=%u", this->current_lsb_, this->shunt_cal_);
|
||||||
|
return this->write_unsigned_16_(RegisterMap::REG_SHUNT_CAL, this->shunt_cal_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::configure_shunt_tempco_() {
|
||||||
|
// Only for 228/229
|
||||||
|
// unsigned 14-bit value
|
||||||
|
// 0x0000 = 0 ppm/°C
|
||||||
|
// 0x3FFF = 16383 ppm/°C
|
||||||
|
if ((this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) &&
|
||||||
|
this->shunt_tempco_ppm_c_ > 0) {
|
||||||
|
return this->write_unsigned_16_(RegisterMap::REG_SHUNT_TEMPCO, this->shunt_tempco_ppm_c_ & 0x3FFF);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_shunt_voltage_mv_(float &volt_out) {
|
||||||
|
// Two's complement value
|
||||||
|
// 228, 229 - 24bit: 20(23-4) + 4(3-0) res
|
||||||
|
// 237, 238, 239 - 16bit
|
||||||
|
|
||||||
|
bool ret{false};
|
||||||
|
float volt_reading{0};
|
||||||
|
uint64_t raw{0};
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_VSHUNT, 3, raw);
|
||||||
|
raw >>= 4;
|
||||||
|
volt_reading = this->two_complement_(raw, 20);
|
||||||
|
} else {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_VSHUNT, 2, raw);
|
||||||
|
volt_reading = this->two_complement_(raw, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret) {
|
||||||
|
volt_out = (this->adc_range_ ? this->cfg_.v_shunt_lsb_range1 : this->cfg_.v_shunt_lsb_range0) * volt_reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_shunt_voltage_mv_ ret=%s, shunt_cal=%d, reading_lsb=%f", OKFAILED(ret), this->shunt_cal_,
|
||||||
|
volt_reading);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_bus_voltage_(float &volt_out) {
|
||||||
|
// Two's complement value
|
||||||
|
// 228, 229 - 24bit: 20(23-4) + 4(3-0) res
|
||||||
|
// 237, 238, 239 - 16bit
|
||||||
|
|
||||||
|
bool ret{false};
|
||||||
|
float volt_reading{0};
|
||||||
|
uint64_t raw{0};
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_VBUS, 3, raw);
|
||||||
|
raw >>= 4;
|
||||||
|
volt_reading = this->two_complement_(raw, 20);
|
||||||
|
} else {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_VBUS, 2, raw);
|
||||||
|
volt_reading = this->two_complement_(raw, 16);
|
||||||
|
}
|
||||||
|
if (ret) {
|
||||||
|
volt_out = this->cfg_.vbus_lsb * (float) volt_reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_bus_voltage_ ret=%s, reading_lsb=%f", OKFAILED(ret), volt_reading);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_die_temp_c_(float &temp_out) {
|
||||||
|
// Two's complement value
|
||||||
|
// 228, 229 - 16bit
|
||||||
|
// 237, 238, 239 - 16bit: 12(15-4) + 4(3-0) res
|
||||||
|
|
||||||
|
bool ret{false};
|
||||||
|
float temp_reading{0};
|
||||||
|
uint64_t raw{0};
|
||||||
|
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_DIETEMP, 2, raw);
|
||||||
|
temp_reading = this->two_complement_(raw, 16);
|
||||||
|
} else {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_DIETEMP, 2, raw);
|
||||||
|
raw >>= 4;
|
||||||
|
temp_reading = this->two_complement_(raw, 12);
|
||||||
|
}
|
||||||
|
if (ret) {
|
||||||
|
temp_out = this->cfg_.die_temp_lsb * (float) temp_reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_die_temp_c_ ret=%s, reading_lsb=%f", OKFAILED(ret), temp_reading);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_current_a_(float &s_out) {
|
||||||
|
// Two's complement value
|
||||||
|
// 228, 229 - 24bit: 20(23-4) + 4(3-0) res
|
||||||
|
// 237, 238, 239 - 16bit
|
||||||
|
bool ret{false};
|
||||||
|
float amps_reading{0};
|
||||||
|
uint64_t raw{0};
|
||||||
|
|
||||||
|
if (this->ina_model_ == INAModel::INA_228 || this->ina_model_ == INAModel::INA_229) {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_CURRENT, 3, raw);
|
||||||
|
raw >>= 4;
|
||||||
|
amps_reading = this->two_complement_(raw, 20);
|
||||||
|
} else {
|
||||||
|
ret = this->read_unsigned_(RegisterMap::REG_CURRENT, 2, raw);
|
||||||
|
amps_reading = this->two_complement_(raw, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_current_a_ ret=%s. current_lsb=%f. reading_lsb=%f", OKFAILED(ret), this->current_lsb_,
|
||||||
|
amps_reading);
|
||||||
|
if (ret) {
|
||||||
|
amps_out = this->current_lsb_ * (float) amps_reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_power_w_(float &power_out) {
|
||||||
|
// Unsigned value
|
||||||
|
// 228, 229 - 24bit
|
||||||
|
// 237, 238, 239 - 24bit
|
||||||
|
uint64_t power_reading{0};
|
||||||
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_POWER, 3, power_reading);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_power_w_ ret=%s, reading_lsb=%d", OKFAILED(ret), (uint32_t) power_reading);
|
||||||
|
if (ret) {
|
||||||
|
power_out = this->cfg_.power_coeff * this->current_lsb_ * (float) power_reading;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_energy_(double &joules_out, double &watt_hours_out) {
|
||||||
|
// Unsigned value
|
||||||
|
// 228, 229 - 40bit
|
||||||
|
// 237, 238, 239 - not available
|
||||||
|
if (this->ina_model_ != INAModel::INA_228 && this->ina_model_ != INAModel::INA_229) {
|
||||||
|
joules_out = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint64_t joules_reading = 0;
|
||||||
|
uint64_t previous_energy = this->energy_overflows_count_ * (((uint64_t) 1) << 40);
|
||||||
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_ENERGY, 5, joules_reading);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_energy_j_ ret=%s, reading_lsb=0x%" PRIX64 ", current_lsb=%f, overflow_cnt=%d", OKFAILED(ret),
|
||||||
|
joules_reading, this->current_lsb_, this->energy_overflows_count_);
|
||||||
|
if (ret) {
|
||||||
|
joules_out = this->cfg_.energy_coeff * this->current_lsb_ * (double) joules_reading + (double) previous_energy;
|
||||||
|
watt_hours_out = joules_out / 3600.0;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_charge_(double &coulombs_out, double &_hours_out) {
|
||||||
|
// Two's complement value
|
||||||
|
// 228, 229 - 40bit
|
||||||
|
// 237, 238, 239 - not available
|
||||||
|
if (this->ina_model_ != INAModel::INA_228 && this->ina_model_ != INAModel::INA_229) {
|
||||||
|
coulombs_out = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// and what to do with this? datasheet doesnt tell us what if charge is negative
|
||||||
|
uint64_t previous_charge = this->charge_overflows_count_ * (((uint64_t) 1) << 39);
|
||||||
|
double coulombs_reading = 0;
|
||||||
|
uint64_t raw{0};
|
||||||
|
auto ret = this->read_unsigned_((uint8_t) RegisterMap::REG_CHARGE, 5, raw);
|
||||||
|
coulombs_reading = this->two_complement_(raw, 40);
|
||||||
|
|
||||||
|
ESP_LOGV(TAG, "read_charge_c_ ret=%d, curr_charge=%f + 39-bit overflow_cnt=%d", ret, coulombs_reading,
|
||||||
|
this->charge_overflows_count_);
|
||||||
|
if (ret) {
|
||||||
|
coulombs_out = this->current_lsb_ * (double) coulombs_reading + (double) previous_charge;
|
||||||
|
amp_hours_out = coulombs_out / 3600.0;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_diagnostics_and_act_() {
|
||||||
|
if (this->ina_model_ != INAModel::INA_228 && this->ina_model_ != INAModel::INA_229) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiagnosticRegister diag{0};
|
||||||
|
auto ret = this->read_unsigned_16_(RegisterMap::REG_DIAG_ALRT, diag.raw_u16);
|
||||||
|
ESP_LOGV(TAG, "read_diagnostics_and_act_ ret=%s, 0x%04X", OKFAILED(ret), diag.raw_u16);
|
||||||
|
|
||||||
|
if (diag.ENERGYOF) {
|
||||||
|
this->energy_overflows_count_++; // 40-bit overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diag.CHARGEOF) {
|
||||||
|
this->charge_overflows_count_++; // 39-bit overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::write_unsigned_16_(uint8_t reg, uint16_t val) {
|
||||||
|
uint16_t data_out = byteswap(val);
|
||||||
|
auto ret = this->write_ina_register(reg, (uint8_t *) &data_out, 2);
|
||||||
|
if (!ret) {
|
||||||
|
ESP_LOGV(TAG, "write_unsigned_16_ FAILED reg=0x%02X, val=0x%04X", reg, val);
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_unsigned_(uint8_t reg, uint8_t reg_size, uint64_t &data_out) {
|
||||||
|
static uint8_t rx_buf[5] = {0}; // max buffer size
|
||||||
|
|
||||||
|
if (reg_size > 5) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ret = this->read_ina_register(reg, rx_buf, reg_size);
|
||||||
|
|
||||||
|
// Combine bytes
|
||||||
|
data_out = rx_buf[0];
|
||||||
|
for (uint8_t i = 1; i < reg_size; i++) {
|
||||||
|
data_out = (data_out << 8) | rx_buf[i];
|
||||||
|
}
|
||||||
|
ESP_LOGV(TAG, "read_unsigned_ reg=0x%02X, ret=%s, len=%d, val=0x%" PRIX64, reg, OKFAILED(ret), reg_size, data_out);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XX::read_unsigned_16_(uint8_t reg, uint16_t &out) {
|
||||||
|
uint16_t data_in{0};
|
||||||
|
auto ret = this->read_ina_register(reg, (uint8_t *) &data_in, 2);
|
||||||
|
out = byteswap(data_in);
|
||||||
|
ESP_LOGV(TAG, "read_unsigned_16_ 0x%02X, ret= %s, val=0x%04X", reg, OKFAILED(ret), out);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t INA2XX::two_complement_(uint64_t value, uint8_t bits) {
|
||||||
|
if (value > (1ULL << (bits - 1))) {
|
||||||
|
return (int64_t) (value - (1ULL << bits));
|
||||||
|
} else {
|
||||||
|
return (int64_t) value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace ina2xx_base
|
||||||
|
} // namespace esphome
|
253
esphome/components/ina2xx_base/ina2xx_base.h
Normal file
253
esphome/components/ina2xx_base/ina2xx_base.h
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_base {
|
||||||
|
|
||||||
|
enum RegisterMap : uint8_t {
|
||||||
|
REG_CONFIG = 0x00,
|
||||||
|
REG_ADC_CONFIG = 0x01,
|
||||||
|
REG_SHUNT_CAL = 0x02,
|
||||||
|
REG_SHUNT_TEMPCO = 0x03,
|
||||||
|
REG_VSHUNT = 0x04,
|
||||||
|
REG_VBUS = 0x05,
|
||||||
|
REG_DIETEMP = 0x06,
|
||||||
|
REG_CURRENT = 0x07,
|
||||||
|
REG_POWER = 0x08,
|
||||||
|
REG_ENERGY = 0x09,
|
||||||
|
REG_CHARGE = 0x0A,
|
||||||
|
REG_DIAG_ALRT = 0x0B,
|
||||||
|
REG_SOVL = 0x0C,
|
||||||
|
REG_SUVL = 0x0D,
|
||||||
|
REG_BOVL = 0x0E,
|
||||||
|
REG_BUVL = 0x0F,
|
||||||
|
REG_TEMP_LIMIT = 0x10,
|
||||||
|
REG_PWR_LIMIT = 0x11,
|
||||||
|
REG_MANUFACTURER_ID = 0x3E,
|
||||||
|
REG_DEVICE_ID = 0x3F
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AdcRange : uint16_t {
|
||||||
|
ADC_RANGE_0 = 0,
|
||||||
|
ADC_RANGE_1 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AdcTime : uint16_t {
|
||||||
|
ADC_TIME_50US = 0,
|
||||||
|
ADC_TIME_84US = 1,
|
||||||
|
ADC_TIME_150US = 2,
|
||||||
|
ADC_TIME_280US = 3,
|
||||||
|
ADC_TIME_540US = 4,
|
||||||
|
ADC_TIME_1052US = 5,
|
||||||
|
ADC_TIME_2074US = 6,
|
||||||
|
ADC_TIME_4120US = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum AdcAvgSamples : uint16_t {
|
||||||
|
ADC_AVG_SAMPLES_1 = 0,
|
||||||
|
ADC_AVG_SAMPLES_4 = 1,
|
||||||
|
ADC_AVG_SAMPLES_16 = 2,
|
||||||
|
ADC_AVG_SAMPLES_64 = 3,
|
||||||
|
ADC_AVG_SAMPLES_128 = 4,
|
||||||
|
ADC_AVG_SAMPLES_256 = 5,
|
||||||
|
ADC_AVG_SAMPLES_512 = 6,
|
||||||
|
ADC_AVG_SAMPLES_1024 = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
union ConfigurationRegister {
|
||||||
|
uint16_t raw_u16;
|
||||||
|
struct {
|
||||||
|
uint16_t reserved_0_3 : 4; // Reserved
|
||||||
|
AdcRange ADCRANGE : 1; // Shunt measurement range 0: ±163.84 mV, 1: ±40.96 mV
|
||||||
|
bool TEMPCOMP : 1; // Temperature compensation enable
|
||||||
|
uint16_t CONVDLY : 8; // Sets the Delay for initial ADC conversion in steps of 2 ms.
|
||||||
|
bool RSTACC : 1; // Reset counters
|
||||||
|
bool RST : 1; // Full device reset
|
||||||
|
} __attribute__((packed));
|
||||||
|
};
|
||||||
|
|
||||||
|
union AdcConfigurationRegister {
|
||||||
|
uint16_t raw_u16;
|
||||||
|
struct {
|
||||||
|
AdcAvgSamples AVG : 3;
|
||||||
|
AdcTime VTCT : 3; // Voltage conversion time
|
||||||
|
AdcTime VSHCT : 3; // Shunt voltage conversion time
|
||||||
|
AdcTime VBUSCT : 3; // Bus voltage conversion time
|
||||||
|
uint16_t MODE : 4;
|
||||||
|
} __attribute__((packed));
|
||||||
|
};
|
||||||
|
|
||||||
|
union TempCompensationRegister {
|
||||||
|
uint16_t raw_u16;
|
||||||
|
struct {
|
||||||
|
uint16_t TEMPCO : 14;
|
||||||
|
uint16_t reserved : 2;
|
||||||
|
} __attribute__((packed));
|
||||||
|
};
|
||||||
|
|
||||||
|
union DiagnosticRegister {
|
||||||
|
uint16_t raw_u16;
|
||||||
|
struct {
|
||||||
|
bool MEMSTAT : 1;
|
||||||
|
bool CNVRF : 1;
|
||||||
|
bool POL : 1;
|
||||||
|
bool BUSUL : 1;
|
||||||
|
bool BUSOL : 1;
|
||||||
|
bool SHNTUL : 1;
|
||||||
|
bool SHNTOL : 1;
|
||||||
|
bool TMPOL : 1;
|
||||||
|
bool RESERVED1 : 1;
|
||||||
|
bool MATHOF : 1;
|
||||||
|
bool CHARGEOF : 1;
|
||||||
|
bool ENERGYOF : 1;
|
||||||
|
bool APOL : 1;
|
||||||
|
bool SLOWALERT : 1;
|
||||||
|
bool CNVR : 1;
|
||||||
|
bool ALATCH : 1;
|
||||||
|
} __attribute__((packed));
|
||||||
|
};
|
||||||
|
|
||||||
|
enum INAModel : uint8_t { INA_UNKNOWN = 0, INA_228, INA_229, INA_238, INA_239, INA_237 };
|
||||||
|
|
||||||
|
class INA2XX : public PollingComponent {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
float get_setup_priority() const override;
|
||||||
|
void update() override;
|
||||||
|
void loop() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
void set_shunt_resistance_ohm(float shunt_resistance_ohm) { this->shunt_resistance_ohm_ = shunt_resistance_ohm; }
|
||||||
|
void set_max_current_a(float max_current_a) { this->max_current_a_ = max_current_a; }
|
||||||
|
void set_adc_range(uint8_t range) { this->adc_range_ = (range == 0) ? AdcRange::ADC_RANGE_0 : AdcRange::ADC_RANGE_1; }
|
||||||
|
void set_adc_time_bus_voltage(AdcTime time) { this->adc_time_bus_voltage_ = time; }
|
||||||
|
void set_adc_time_shunt_voltage(AdcTime time) { this->adc_time_shunt_voltage_ = time; }
|
||||||
|
void set_adc_time_die_temperature(AdcTime time) { this->adc_time_die_temperature_ = time; }
|
||||||
|
void set_adc_avg_samples(AdcAvgSamples samples) { this->adc_avg_samples_ = samples; }
|
||||||
|
void set_shunt_tempco(uint16_t coeff) { this->shunt_tempco_ppm_c_ = coeff; }
|
||||||
|
|
||||||
|
void set_shunt_voltage_sensor(sensor::Sensor *sensor) { this->shunt_voltage_sensor_ = sensor; }
|
||||||
|
void set_bus_voltage_sensor(sensor::Sensor *sensor) { this->bus_voltage_sensor_ = sensor; }
|
||||||
|
void set_die_temperature_sensor(sensor::Sensor *sensor) { this->die_temperature_sensor_ = sensor; }
|
||||||
|
void set_current_sensor(sensor::Sensor *sensor) { this->current_sensor_ = sensor; }
|
||||||
|
void set_power_sensor(sensor::Sensor *sensor) { this->power_sensor_ = sensor; }
|
||||||
|
void set_energy_sensor_j(sensor::Sensor *sensor) { this->energy_sensor_j_ = sensor; }
|
||||||
|
void set_energy_sensor_wh(sensor::Sensor *sensor) { this->energy_sensor_wh_ = sensor; }
|
||||||
|
void set_charge_sensor_c(sensor::Sensor *sensor) { this->charge_sensor_c_ = sensor; }
|
||||||
|
void set_charge_sensor_ah(sensor::Sensor *sensor) { this->charge_sensor_ah_ = sensor; }
|
||||||
|
|
||||||
|
void set_model(INAModel model) { this->ina_model_ = model; }
|
||||||
|
|
||||||
|
bool reset_energy_counters();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool reset_config_();
|
||||||
|
bool check_device_model_();
|
||||||
|
bool configure_adc_();
|
||||||
|
|
||||||
|
bool configure_shunt_();
|
||||||
|
bool configure_shunt_tempco_();
|
||||||
|
bool configure_adc_range_();
|
||||||
|
|
||||||
|
bool read_shunt_voltage_mv_(float &volt_out);
|
||||||
|
bool read_bus_voltage_(float &volt_out);
|
||||||
|
bool read_die_temp_c_(float &temp);
|
||||||
|
bool read_current_a_(float &s_out);
|
||||||
|
bool read_power_w_(float &power_out);
|
||||||
|
bool read_energy_(double &joules_out, double &watt_hours_out);
|
||||||
|
bool read_charge_(double &coulombs_out, double &_hours_out);
|
||||||
|
|
||||||
|
bool read_diagnostics_and_act_();
|
||||||
|
|
||||||
|
//
|
||||||
|
// User configuration
|
||||||
|
//
|
||||||
|
float shunt_resistance_ohm_;
|
||||||
|
float max_current_a_;
|
||||||
|
AdcRange adc_range_{AdcRange::ADC_RANGE_0};
|
||||||
|
AdcTime adc_time_bus_voltage_{AdcTime::ADC_TIME_4120US};
|
||||||
|
AdcTime adc_time_shunt_voltage_{AdcTime::ADC_TIME_4120US};
|
||||||
|
AdcTime adc_time_die_temperature_{AdcTime::ADC_TIME_4120US};
|
||||||
|
AdcAvgSamples adc_avg_samples_{AdcAvgSamples::ADC_AVG_SAMPLES_128};
|
||||||
|
uint16_t shunt_tempco_ppm_c_{0};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Calculated coefficients
|
||||||
|
//
|
||||||
|
uint16_t shunt_cal_{0};
|
||||||
|
float current_lsb_{0};
|
||||||
|
|
||||||
|
uint32_t energy_overflows_count_{0};
|
||||||
|
uint32_t charge_overflows_count_{0};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Sensor objects
|
||||||
|
//
|
||||||
|
sensor::Sensor *shunt_voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||||
|
sensor::Sensor *die_temperature_sensor_{nullptr};
|
||||||
|
sensor::Sensor *current_sensor_{nullptr};
|
||||||
|
sensor::Sensor *power_sensor_{nullptr};
|
||||||
|
sensor::Sensor *energy_sensor_j_{nullptr};
|
||||||
|
sensor::Sensor *energy_sensor_wh_{nullptr};
|
||||||
|
sensor::Sensor *charge_sensor_c_{nullptr};
|
||||||
|
sensor::Sensor *charge_sensor_ah_{nullptr};
|
||||||
|
|
||||||
|
//
|
||||||
|
// FSM states
|
||||||
|
//
|
||||||
|
enum class State : uint8_t {
|
||||||
|
NOT_INITIALIZED = 0x0,
|
||||||
|
IDLE,
|
||||||
|
DATA_COLLECTION_1,
|
||||||
|
DATA_COLLECTION_2,
|
||||||
|
DATA_COLLECTION_3,
|
||||||
|
DATA_COLLECTION_4,
|
||||||
|
DATA_COLLECTION_5,
|
||||||
|
DATA_COLLECTION_6,
|
||||||
|
DATA_COLLECTION_7,
|
||||||
|
DATA_COLLECTION_8,
|
||||||
|
} state_{State::NOT_INITIALIZED};
|
||||||
|
|
||||||
|
bool full_loop_is_okay_{true};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Device model
|
||||||
|
//
|
||||||
|
INAModel ina_model_{INAModel::INA_UNKNOWN};
|
||||||
|
uint16_t dev_id_{0};
|
||||||
|
bool device_mismatch_{false};
|
||||||
|
|
||||||
|
//
|
||||||
|
// Device specific parameters
|
||||||
|
//
|
||||||
|
struct {
|
||||||
|
float vbus_lsb;
|
||||||
|
float v_shunt_lsb_range0;
|
||||||
|
float v_shunt_lsb_range1;
|
||||||
|
float shunt_cal_scale;
|
||||||
|
int8_t current_lsb_scale_factor;
|
||||||
|
float die_temp_lsb;
|
||||||
|
float power_coeff;
|
||||||
|
float energy_coeff;
|
||||||
|
} cfg_;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Register read/write
|
||||||
|
//
|
||||||
|
bool read_unsigned_(uint8_t reg, uint8_t reg_size, uint64_t &data_out);
|
||||||
|
bool read_unsigned_16_(uint8_t reg, uint16_t &out);
|
||||||
|
bool write_unsigned_16_(uint8_t reg, uint16_t val);
|
||||||
|
|
||||||
|
int64_t two_complement_(uint64_t value, uint8_t bits);
|
||||||
|
|
||||||
|
//
|
||||||
|
// Interface-specific implementation
|
||||||
|
//
|
||||||
|
virtual bool read_ina_register(uint8_t a_register, uint8_t *data, size_t len) = 0;
|
||||||
|
virtual bool write_ina_register(uint8_t a_register, const uint8_t *data, size_t len) = 0;
|
||||||
|
};
|
||||||
|
} // namespace ina2xx_base
|
||||||
|
} // namespace esphome
|
0
esphome/components/ina2xx_i2c/__init__.py
Normal file
0
esphome/components/ina2xx_i2c/__init__.py
Normal file
39
esphome/components/ina2xx_i2c/ina2xx_i2c.cpp
Normal file
39
esphome/components/ina2xx_i2c/ina2xx_i2c.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#include "ina2xx_i2c.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_i2c {
|
||||||
|
|
||||||
|
static const char *const TAG = "ina2xx_i2c";
|
||||||
|
|
||||||
|
void INA2XXI2C::setup() {
|
||||||
|
auto err = this->write(nullptr, 0);
|
||||||
|
if (err != i2c::ERROR_OK) {
|
||||||
|
this->mark_failed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
INA2XX::setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA2XXI2C::dump_config() {
|
||||||
|
INA2XX::dump_config();
|
||||||
|
LOG_I2C_DEVICE(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XXI2C::read_ina_register(uint8_t reg, uint8_t *data, size_t len) {
|
||||||
|
auto ret = this->read_register(reg, data, len, false);
|
||||||
|
if (ret != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "read_ina_register_ failed. Reg=0x%02X Err=%d", reg, ret);
|
||||||
|
}
|
||||||
|
return ret == i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XXI2C::write_ina_register(uint8_t reg, const uint8_t *data, size_t len) {
|
||||||
|
auto ret = this->write_register(reg, data, len);
|
||||||
|
if (ret != i2c::ERROR_OK) {
|
||||||
|
ESP_LOGE(TAG, "write_register failed. Reg=0x%02X Err=%d", reg, ret);
|
||||||
|
}
|
||||||
|
return ret == i2c::ERROR_OK;
|
||||||
|
}
|
||||||
|
} // namespace ina2xx_i2c
|
||||||
|
} // namespace esphome
|
21
esphome/components/ina2xx_i2c/ina2xx_i2c.h
Normal file
21
esphome/components/ina2xx_i2c/ina2xx_i2c.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/ina2xx_base/ina2xx_base.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_i2c {
|
||||||
|
|
||||||
|
class INA2XXI2C : public ina2xx_base::INA2XX, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool read_ina_register(uint8_t reg, uint8_t *data, size_t len) override;
|
||||||
|
bool write_ina_register(uint8_t reg, const uint8_t *data, size_t len) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ina2xx_i2c
|
||||||
|
} // namespace esphome
|
34
esphome/components/ina2xx_i2c/sensor.py
Normal file
34
esphome/components/ina2xx_i2c/sensor.py
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import ina2xx_base, i2c
|
||||||
|
from esphome.const import CONF_ID, CONF_MODEL
|
||||||
|
|
||||||
|
AUTO_LOAD = ["ina2xx_base"]
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
|
||||||
|
ina2xx_i2c = cg.esphome_ns.namespace("ina2xx_i2c")
|
||||||
|
INA2XX_I2C = ina2xx_i2c.class_("INA2XXI2C", ina2xx_base.INA2XX, i2c.I2CDevice)
|
||||||
|
|
||||||
|
INAModel = ina2xx_base.ina2xx_base_ns.enum("INAModel")
|
||||||
|
INA_MODELS = {
|
||||||
|
"INA228": INAModel.INA_228,
|
||||||
|
"INA238": INAModel.INA_238,
|
||||||
|
"INA237": INAModel.INA_237,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
ina2xx_base.INA2XX_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(INA2XX_I2C),
|
||||||
|
cv.Required(CONF_MODEL): cv.enum(INA_MODELS, upper=True),
|
||||||
|
}
|
||||||
|
).extend(i2c.i2c_device_schema(0x40)),
|
||||||
|
ina2xx_base.validate_model_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await ina2xx_base.setup_ina2xx(var, config)
|
||||||
|
await i2c.register_i2c_device(var, config)
|
0
esphome/components/ina2xx_spi/__init__.py
Normal file
0
esphome/components/ina2xx_spi/__init__.py
Normal file
38
esphome/components/ina2xx_spi/ina2xx_spi.cpp
Normal file
38
esphome/components/ina2xx_spi/ina2xx_spi.cpp
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
#include "ina2xx_spi.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_spi {
|
||||||
|
|
||||||
|
static const char *const TAG = "ina2xx_spi";
|
||||||
|
|
||||||
|
void INA2XXSPI::setup() {
|
||||||
|
this->spi_setup();
|
||||||
|
INA2XX::setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void INA2XXSPI::dump_config() {
|
||||||
|
INA2XX::dump_config();
|
||||||
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XXSPI::read_ina_register(uint8_t reg, uint8_t *data, size_t len) {
|
||||||
|
reg = (reg << 2); // top 6 bits
|
||||||
|
reg |= 0x01; // read
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg);
|
||||||
|
this->read_array(data, len);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool INA2XXSPI::write_ina_register(uint8_t reg, const uint8_t *data, size_t len) {
|
||||||
|
reg = (reg << 2); // top 6 bits
|
||||||
|
this->enable();
|
||||||
|
this->write_byte(reg);
|
||||||
|
this->write_array(data, len);
|
||||||
|
this->disable();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace ina2xx_spi
|
||||||
|
} // namespace esphome
|
22
esphome/components/ina2xx_spi/ina2xx_spi.h
Normal file
22
esphome/components/ina2xx_spi/ina2xx_spi.h
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/components/ina2xx_base/ina2xx_base.h"
|
||||||
|
#include "esphome/components/spi/spi.h"
|
||||||
|
|
||||||
|
namespace esphome {
|
||||||
|
namespace ina2xx_spi {
|
||||||
|
|
||||||
|
class INA2XXSPI : public ina2xx_base::INA2XX,
|
||||||
|
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_TRAILING,
|
||||||
|
spi::DATA_RATE_1MHZ> {
|
||||||
|
public:
|
||||||
|
void setup() override;
|
||||||
|
void dump_config() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool read_ina_register(uint8_t reg, uint8_t *data, size_t len) override;
|
||||||
|
bool write_ina_register(uint8_t reg, const uint8_t *data, size_t len) override;
|
||||||
|
};
|
||||||
|
} // namespace ina2xx_spi
|
||||||
|
} // namespace esphome
|
33
esphome/components/ina2xx_spi/sensor.py
Normal file
33
esphome/components/ina2xx_spi/sensor.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import ina2xx_base, spi
|
||||||
|
from esphome.const import CONF_ID, CONF_MODEL
|
||||||
|
|
||||||
|
AUTO_LOAD = ["ina2xx_base"]
|
||||||
|
CODEOWNERS = ["@latonita"]
|
||||||
|
DEPENDENCIES = ["spi"]
|
||||||
|
|
||||||
|
ina2xx_spi = cg.esphome_ns.namespace("ina2xx_spi")
|
||||||
|
INA2XX_SPI = ina2xx_spi.class_("INA2XXSPI", ina2xx_base.INA2XX, spi.SPIDevice)
|
||||||
|
|
||||||
|
INAModel = ina2xx_base.ina2xx_base_ns.enum("INAModel")
|
||||||
|
INA_MODELS = {
|
||||||
|
"INA229": INAModel.INA_229,
|
||||||
|
"INA239": INAModel.INA_239,
|
||||||
|
}
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = cv.All(
|
||||||
|
ina2xx_base.INA2XX_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(INA2XX_SPI),
|
||||||
|
cv.Required(CONF_MODEL): cv.enum(INA_MODELS, upper=True),
|
||||||
|
}
|
||||||
|
).extend(spi.spi_device_schema(cs_pin_required=True)),
|
||||||
|
ina2xx_base.validate_model_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def to_code(config):
|
||||||
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
|
await ina2xx_base.setup_ina2xx(var, config)
|
||||||
|
await spi.register_spi_device(var, config)
|
20
tests/components/ina2xx_i2c/common.yaml
Normal file
20
tests/components/ina2xx_i2c/common.yaml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
i2c:
|
||||||
|
- id: i2c_ina2xx
|
||||||
|
scl: ${scl_pin}
|
||||||
|
sda: ${sda_pin}
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: ina2xx_i2c
|
||||||
|
i2c_id: i2c_ina2xx
|
||||||
|
address: 0x40
|
||||||
|
model: INA228
|
||||||
|
shunt_resistance: 0.001130 ohm
|
||||||
|
max_current: 40 A
|
||||||
|
adc_range: 1
|
||||||
|
temperature_coefficient: 50
|
||||||
|
shunt_voltage: "INA2xx Shunt Voltage"
|
||||||
|
bus_voltage: "INA2xx Bus Voltage"
|
||||||
|
current: "INA2xx Current"
|
||||||
|
power: "INA2xx Power"
|
||||||
|
energy: "INA2xx Energy"
|
||||||
|
charge: "INA2xx Charge"
|
5
tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml
Normal file
5
tests/components/ina2xx_i2c/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/ina2xx_i2c/test.esp32-c3.yaml
Normal file
5
tests/components/ina2xx_i2c/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/ina2xx_i2c/test.esp32-idf.yaml
Normal file
5
tests/components/ina2xx_i2c/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO16
|
||||||
|
sda_pin: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/ina2xx_i2c/test.esp32.yaml
Normal file
5
tests/components/ina2xx_i2c/test.esp32.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO16
|
||||||
|
sda_pin: GPIO17
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/ina2xx_i2c/test.esp8266.yaml
Normal file
5
tests/components/ina2xx_i2c/test.esp8266.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
5
tests/components/ina2xx_i2c/test.rp2040.yaml
Normal file
5
tests/components/ina2xx_i2c/test.rp2040.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
substitutions:
|
||||||
|
scl_pin: GPIO5
|
||||||
|
sda_pin: GPIO4
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
21
tests/components/ina2xx_spi/common.yaml
Normal file
21
tests/components/ina2xx_spi/common.yaml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
spi:
|
||||||
|
- id: spi_ina2xx
|
||||||
|
clk_pin: ${clk_pin}
|
||||||
|
mosi_pin: ${mosi_pin}
|
||||||
|
miso_pin: ${miso_pin}
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: ina2xx_spi
|
||||||
|
spi_id: spi_ina2xx
|
||||||
|
cs_pin: ${cs_pin}
|
||||||
|
model: INA229
|
||||||
|
shunt_resistance: 0.001130 ohm
|
||||||
|
max_current: 40 A
|
||||||
|
adc_range: 1
|
||||||
|
temperature_coefficient: 50
|
||||||
|
shunt_voltage: "INA2xx Shunt Voltage"
|
||||||
|
bus_voltage: "INA2xx Bus Voltage"
|
||||||
|
current: "INA2xx Current"
|
||||||
|
power: "INA2xx Power"
|
||||||
|
energy: "INA2xx Energy"
|
||||||
|
charge: "INA2xx Charge"
|
7
tests/components/ina2xx_spi/test.esp32-c3-idf.yaml
Normal file
7
tests/components/ina2xx_spi/test.esp32-c3-idf.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO6
|
||||||
|
mosi_pin: GPIO7
|
||||||
|
miso_pin: GPIO5
|
||||||
|
cs_pin: GPIO8
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
7
tests/components/ina2xx_spi/test.esp32-c3.yaml
Normal file
7
tests/components/ina2xx_spi/test.esp32-c3.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO6
|
||||||
|
mosi_pin: GPIO7
|
||||||
|
miso_pin: GPIO5
|
||||||
|
cs_pin: GPIO8
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
7
tests/components/ina2xx_spi/test.esp32-idf.yaml
Normal file
7
tests/components/ina2xx_spi/test.esp32-idf.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO16
|
||||||
|
mosi_pin: GPIO17
|
||||||
|
miso_pin: GPIO15
|
||||||
|
cs_pin: GPIO5
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
7
tests/components/ina2xx_spi/test.esp32.yaml
Normal file
7
tests/components/ina2xx_spi/test.esp32.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO16
|
||||||
|
mosi_pin: GPIO17
|
||||||
|
miso_pin: GPIO15
|
||||||
|
cs_pin: GPIO5
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
7
tests/components/ina2xx_spi/test.esp8266.yaml
Normal file
7
tests/components/ina2xx_spi/test.esp8266.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO14
|
||||||
|
mosi_pin: GPIO13
|
||||||
|
miso_pin: GPIO12
|
||||||
|
cs_pin: GPIO15
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
7
tests/components/ina2xx_spi/test.rp2040.yaml
Normal file
7
tests/components/ina2xx_spi/test.rp2040.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
substitutions:
|
||||||
|
clk_pin: GPIO2
|
||||||
|
mosi_pin: GPIO3
|
||||||
|
miso_pin: GPIO4
|
||||||
|
cs_pin: GPIO5
|
||||||
|
|
||||||
|
<<: !include common.yaml
|
Loading…
Reference in a new issue