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:
Anton Viktorov
2024-05-16 06:50:28 +02:00
committed by GitHub
parent 034c196ad8
commit b06e0746f5
26 changed files with 1415 additions and 0 deletions
+3
View File
@@ -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
View 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))
File diff suppressed because it is too large Load Diff
@@ -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 &amps_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 &amp_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,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
@@ -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
View 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,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
@@ -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
View 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
View 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"
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO16
sda_pin: GPIO17
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
scl_pin: GPIO5
sda_pin: GPIO4
<<: !include common.yaml
+21
View 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"
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO6
mosi_pin: GPIO7
miso_pin: GPIO5
cs_pin: GPIO8
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO6
mosi_pin: GPIO7
miso_pin: GPIO5
cs_pin: GPIO8
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO16
mosi_pin: GPIO17
miso_pin: GPIO15
cs_pin: GPIO5
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO16
mosi_pin: GPIO17
miso_pin: GPIO15
cs_pin: GPIO5
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO14
mosi_pin: GPIO13
miso_pin: GPIO12
cs_pin: GPIO15
<<: !include common.yaml
@@ -0,0 +1,7 @@
substitutions:
clk_pin: GPIO2
mosi_pin: GPIO3
miso_pin: GPIO4
cs_pin: GPIO5
<<: !include common.yaml