mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[INA3221] Add optional flags to expose all functionality (#16309)
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: J. Nick Koston <nick@home-assistant.io> Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
co-authored by
pre-commit-ci-lite[bot]
J. Nick Koston
J. Nick Koston
parent
753c10e5ea
commit
74e96b7da6
@@ -13,6 +13,24 @@ static const uint8_t INA3221_REGISTER_CHANNEL2_SHUNT_VOLTAGE = 0x03;
|
||||
static const uint8_t INA3221_REGISTER_CHANNEL2_BUS_VOLTAGE = 0x04;
|
||||
static const uint8_t INA3221_REGISTER_CHANNEL3_SHUNT_VOLTAGE = 0x05;
|
||||
static const uint8_t INA3221_REGISTER_CHANNEL3_BUS_VOLTAGE = 0x06;
|
||||
#ifdef USE_INA3221_ALERT_LIMITS
|
||||
static constexpr uint8_t INA3221_REGISTER_CHANNEL1_CRITICAL_ALERT = 0x07;
|
||||
static constexpr uint8_t INA3221_REGISTER_CHANNEL1_WARNING_ALERT = 0x0A;
|
||||
#endif
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
static constexpr uint8_t INA3221_REGISTER_SHUNT_VOLTAGE_SUM = 0x0D;
|
||||
static constexpr uint8_t INA3221_REGISTER_MASK_ENABLE = 0x0F;
|
||||
#endif
|
||||
|
||||
// Configuration register: bit 15 resets the chip, bits 14..12 enable channels 1..3 (the same bit
|
||||
// positions select channels for summation in the mask/enable register) and bits 2..0 set the mode.
|
||||
// Code generation fills in the averaging and conversion time fields.
|
||||
static constexpr uint16_t INA3221_CONFIG_RESET = 0x8000;
|
||||
static constexpr uint16_t INA3221_CONFIG_CHANNEL1_ENABLE = 0x4000;
|
||||
static constexpr uint16_t INA3221_CONFIG_CHANNEL_MASK = 0x7000;
|
||||
static constexpr uint16_t INA3221_CONFIG_MODE_MASK = 0x0007;
|
||||
|
||||
static constexpr uint32_t READ_TIMEOUT_ID = 0;
|
||||
|
||||
// Addresses:
|
||||
// A0 = GND -> 0x40
|
||||
@@ -21,37 +39,38 @@ static const uint8_t INA3221_REGISTER_CHANNEL3_BUS_VOLTAGE = 0x06;
|
||||
// A0 = SCL -> 0x43
|
||||
|
||||
void INA3221Component::setup() {
|
||||
// Config Register
|
||||
// 0bx000000000000000 << 15 RESET Bit (1 -> trigger reset)
|
||||
if (!this->write_byte_16(INA3221_REGISTER_CONFIG, 0x8000)) {
|
||||
if (!this->write_byte_16(INA3221_REGISTER_CONFIG, INA3221_CONFIG_RESET)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
delay(1);
|
||||
|
||||
uint16_t config = 0;
|
||||
// 0b0xxx000000000000 << 12 Channel Enables (1 -> ON)
|
||||
if (this->channels_[0].exists()) {
|
||||
config |= 0b0100000000000000;
|
||||
}
|
||||
if (this->channels_[1].exists()) {
|
||||
config |= 0b0010000000000000;
|
||||
}
|
||||
if (this->channels_[2].exists()) {
|
||||
config |= 0b0001000000000000;
|
||||
}
|
||||
// 0b0000xxx000000000 << 9 Averaging Mode (0 -> 1 sample, 111 -> 1024 samples)
|
||||
config |= 0b0000000000000000;
|
||||
// 0b0000000xxx000000 << 6 Bus Voltage Conversion time (100 -> 1.1ms, 111 -> 8.244 ms)
|
||||
config |= 0b0000000111000000;
|
||||
// 0b0000000000xxx000 << 3 Shunt Voltage Conversion time (same as above)
|
||||
config |= 0b0000000000111000;
|
||||
// 0b0000000000000xxx << 0 Operating mode (111 -> Shunt and bus, continuous)
|
||||
config |= 0b0000000000000111;
|
||||
if (!this->write_byte_16(INA3221_REGISTER_CONFIG, config)) {
|
||||
if (!this->write_byte_16(INA3221_REGISTER_CONFIG, this->config_)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef USE_INA3221_ALERT_LIMITS
|
||||
for (uint8_t i = 0; i < 3; i++) {
|
||||
const INA3221Channel &channel = this->channels_[i];
|
||||
if ((channel.critical_limit_ != 0 &&
|
||||
!this->write_byte_16(INA3221_REGISTER_CHANNEL1_CRITICAL_ALERT + i, channel.critical_limit_)) ||
|
||||
(channel.warning_limit_ != 0 &&
|
||||
!this->write_byte_16(INA3221_REGISTER_CHANNEL1_WARNING_ALERT + i, channel.warning_limit_))) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
// Sum the shunt voltages of every enabled channel
|
||||
if (this->sum_shunt_voltage_sensor_ != nullptr &&
|
||||
!this->write_byte_16(INA3221_REGISTER_MASK_ENABLE, this->config_ & INA3221_CONFIG_CHANNEL_MASK)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void INA3221Component::dump_config() {
|
||||
@@ -61,6 +80,10 @@ void INA3221Component::dump_config() {
|
||||
ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL);
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
ESP_LOGCONFIG(TAG, " Config register: 0x%04X", this->config_);
|
||||
if (this->single_shot_wait_ms_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Single-shot conversion: %u ms", this->single_shot_wait_ms_);
|
||||
}
|
||||
|
||||
LOG_SENSOR(" ", "Bus Voltage #1", this->channels_[0].bus_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Shunt Voltage #1", this->channels_[0].shunt_voltage_sensor_);
|
||||
@@ -74,6 +97,11 @@ void INA3221Component::dump_config() {
|
||||
LOG_SENSOR(" ", "Shunt Voltage #3", this->channels_[2].shunt_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Current #3", this->channels_[2].current_sensor_);
|
||||
LOG_SENSOR(" ", "Power #3", this->channels_[2].power_sensor_);
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
LOG_SENSOR(" ", "Sum Shunt Voltage", this->sum_shunt_voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Sum Current", this->sum_current_sensor_);
|
||||
LOG_SENSOR(" ", "Sum Power", this->sum_power_sensor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline uint8_t ina3221_bus_voltage_register(int channel) { return 0x02 + channel * 2; }
|
||||
@@ -81,11 +109,36 @@ inline uint8_t ina3221_bus_voltage_register(int channel) { return 0x02 + channel
|
||||
inline uint8_t ina3221_shunt_voltage_register(int channel) { return 0x01 + channel * 2; }
|
||||
|
||||
void INA3221Component::update() {
|
||||
if (this->single_shot_wait_ms_ == 0) {
|
||||
this->read_data_();
|
||||
return;
|
||||
}
|
||||
// In single-shot mode, writing the configuration register starts one conversion of every channel
|
||||
if (!this->write_byte_16(INA3221_REGISTER_CONFIG, this->config_)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->set_timeout(READ_TIMEOUT_ID, this->single_shot_wait_ms_, [this]() { this->read_data_(); });
|
||||
}
|
||||
|
||||
void INA3221Component::read_data_() {
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
// The software sums need every enabled channel's readings, not only the ones with their own sensors
|
||||
const bool sum_all = this->sum_current_sensor_ != nullptr || this->sum_power_sensor_ != nullptr;
|
||||
float total_current_a = 0.0f;
|
||||
float total_power_w = 0.0f;
|
||||
#else
|
||||
constexpr bool sum_all = false;
|
||||
#endif
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if ((this->config_ & (INA3221_CONFIG_CHANNEL1_ENABLE >> i)) == 0) {
|
||||
continue;
|
||||
}
|
||||
INA3221Channel &channel = this->channels_[i];
|
||||
float bus_voltage_v = NAN, current_a = NAN;
|
||||
uint16_t raw;
|
||||
if (channel.should_measure_bus_voltage()) {
|
||||
if (sum_all || channel.should_measure_bus_voltage()) {
|
||||
if (!this->read_byte_16(ina3221_bus_voltage_register(i), &raw)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -94,7 +147,7 @@ void INA3221Component::update() {
|
||||
if (channel.bus_voltage_sensor_ != nullptr)
|
||||
channel.bus_voltage_sensor_->publish_state(bus_voltage_v);
|
||||
}
|
||||
if (channel.should_measure_shunt_voltage()) {
|
||||
if (sum_all || channel.should_measure_shunt_voltage()) {
|
||||
if (!this->read_byte_16(ina3221_shunt_voltage_register(i), &raw)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -109,17 +162,42 @@ void INA3221Component::update() {
|
||||
if (channel.power_sensor_ != nullptr) {
|
||||
channel.power_sensor_->publish_state(bus_voltage_v * current_a);
|
||||
}
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
if (sum_all) {
|
||||
total_current_a += current_a;
|
||||
total_power_w += bus_voltage_v * current_a;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
if (this->sum_shunt_voltage_sensor_ != nullptr) {
|
||||
uint16_t raw;
|
||||
if (!this->read_byte_16(INA3221_REGISTER_SHUNT_VOLTAGE_SUM, &raw)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
// 40 uV per step, held in bits 15..1
|
||||
this->sum_shunt_voltage_sensor_->publish_state(int16_t(raw) * 40.0f / 2.0f / 1000000.0f);
|
||||
}
|
||||
if (this->sum_current_sensor_ != nullptr) {
|
||||
this->sum_current_sensor_->publish_state(total_current_a);
|
||||
}
|
||||
if (this->sum_power_sensor_ != nullptr) {
|
||||
this->sum_power_sensor_->publish_state(total_power_w);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void INA3221Component::on_powerdown() {
|
||||
// Mode 0 powers the chip down; setup() resets it on the next boot
|
||||
this->write_byte_16(INA3221_REGISTER_CONFIG, static_cast<uint16_t>(this->config_ & ~INA3221_CONFIG_MODE_MASK));
|
||||
}
|
||||
|
||||
void INA3221Component::set_shunt_resistance(int channel, float resistance_ohm) {
|
||||
this->channels_[channel].shunt_resistance_ = resistance_ohm;
|
||||
}
|
||||
|
||||
bool INA3221Component::INA3221Channel::exists() {
|
||||
return this->bus_voltage_sensor_ != nullptr || this->shunt_voltage_sensor_ != nullptr ||
|
||||
this->current_sensor_ != nullptr || this->power_sensor_ != nullptr;
|
||||
}
|
||||
bool INA3221Component::INA3221Channel::should_measure_shunt_voltage() {
|
||||
return this->shunt_voltage_sensor_ != nullptr || this->current_sensor_ != nullptr || this->power_sensor_ != nullptr;
|
||||
}
|
||||
|
||||
@@ -7,10 +7,17 @@
|
||||
namespace esphome::ina3221 {
|
||||
|
||||
class INA3221Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
#ifdef USE_INA3221_SUMMATION
|
||||
SUB_SENSOR(sum_shunt_voltage)
|
||||
SUB_SENSOR(sum_current)
|
||||
SUB_SENSOR(sum_power)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
void on_powerdown() override;
|
||||
|
||||
void set_bus_voltage_sensor(int channel, sensor::Sensor *obj) { this->channels_[channel].bus_voltage_sensor_ = obj; }
|
||||
void set_shunt_voltage_sensor(int channel, sensor::Sensor *obj) {
|
||||
@@ -19,19 +26,35 @@ class INA3221Component final : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_current_sensor(int channel, sensor::Sensor *obj) { this->channels_[channel].current_sensor_ = obj; }
|
||||
void set_power_sensor(int channel, sensor::Sensor *obj) { this->channels_[channel].power_sensor_ = obj; }
|
||||
void set_shunt_resistance(int channel, float resistance_ohm);
|
||||
/// Configuration register value built by code generation: channel enables, averaging, conversion times, mode
|
||||
void set_config_register(uint16_t config) { this->config_ = config; }
|
||||
/// Time one single-shot conversion of every enabled channel takes; zero means continuous mode
|
||||
void set_single_shot_wait_ms(uint16_t wait_ms) { this->single_shot_wait_ms_ = wait_ms; }
|
||||
#ifdef USE_INA3221_ALERT_LIMITS
|
||||
void set_warning_limit(int channel, uint16_t reg) { this->channels_[channel].warning_limit_ = reg; }
|
||||
void set_critical_limit(int channel, uint16_t reg) { this->channels_[channel].critical_limit_ = reg; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void read_data_();
|
||||
|
||||
struct INA3221Channel {
|
||||
float shunt_resistance_{0.1f};
|
||||
sensor::Sensor *bus_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *shunt_voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *power_sensor_{nullptr};
|
||||
#ifdef USE_INA3221_ALERT_LIMITS
|
||||
// Alert limit register values; zero keeps the chip default, which never trips
|
||||
uint16_t warning_limit_{0};
|
||||
uint16_t critical_limit_{0};
|
||||
#endif
|
||||
|
||||
bool exists();
|
||||
bool should_measure_shunt_voltage();
|
||||
bool should_measure_bus_voltage();
|
||||
} channels_[3];
|
||||
uint16_t config_{0};
|
||||
uint16_t single_shot_wait_ms_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::ina3221
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from typing import Any
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components.const import CONF_ADC_AVERAGING, CONF_ADC_TIME
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BUS_VOLTAGE,
|
||||
@@ -8,6 +11,7 @@ from esphome.const import (
|
||||
CONF_POWER,
|
||||
CONF_SHUNT_RESISTANCE,
|
||||
CONF_SHUNT_VOLTAGE,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
@@ -23,69 +27,195 @@ DEPENDENCIES = ["i2c"]
|
||||
CONF_CHANNEL_1 = "channel_1"
|
||||
CONF_CHANNEL_2 = "channel_2"
|
||||
CONF_CHANNEL_3 = "channel_3"
|
||||
CONF_CONTINUOUS_MODE = "continuous_mode"
|
||||
CONF_WARNING_CURRENT_LIMIT = "warning_current_limit"
|
||||
CONF_CRITICAL_CURRENT_LIMIT = "critical_current_limit"
|
||||
CONF_SUM_SHUNT_VOLTAGE = "sum_shunt_voltage"
|
||||
CONF_SUM_CURRENT = "sum_current"
|
||||
CONF_SUM_POWER = "sum_power"
|
||||
|
||||
CHANNELS = (CONF_CHANNEL_1, CONF_CHANNEL_2, CONF_CHANNEL_3)
|
||||
|
||||
# Configuration register layout: channel enables in bits 14..12, averaging in 11..9,
|
||||
# bus conversion time in 8..6, shunt conversion time in 5..3, operating mode in 2..0
|
||||
CHANNEL_ENABLE_BITS = (1 << 14, 1 << 13, 1 << 12)
|
||||
ADC_AVERAGING_SHIFT = 9
|
||||
ADC_TIME_BUS_SHIFT = 6
|
||||
ADC_TIME_SHUNT_SHIFT = 3
|
||||
MODE_SINGLE_SHOT = 0b011
|
||||
MODE_CONTINUOUS = 0b111
|
||||
|
||||
# Register field value by sample count and by conversion time in microseconds
|
||||
ADC_AVERAGING = {1: 0, 4: 1, 16: 2, 64: 3, 128: 4, 256: 5, 512: 6, 1024: 7}
|
||||
ADC_TIMES_US = {140: 0, 204: 1, 332: 2, 588: 3, 1100: 4, 2116: 5, 4156: 6, 8244: 7}
|
||||
|
||||
# Alert limits are shunt voltages in 40 uV steps, held in bits 15..3
|
||||
ALERT_LIMIT_LSB_V = 40e-6
|
||||
ALERT_LIMIT_MAX_V = 0x0FFF * ALERT_LIMIT_LSB_V
|
||||
# Headroom on top of the computed conversion time before the results are read
|
||||
SINGLE_SHOT_MARGIN_MS = 5
|
||||
|
||||
ina3221_ns = cg.esphome_ns.namespace("ina3221")
|
||||
INA3221Component = ina3221_ns.class_(
|
||||
"INA3221Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
INA3221_CHANNEL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_BUS_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SHUNT_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(
|
||||
cv.resistance, cv.Range(min=0.0, max=32.0)
|
||||
),
|
||||
}
|
||||
VOLTAGE_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
CURRENT_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
POWER_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
CURRENT_LIMIT_SCHEMA = cv.All(cv.current, cv.Range(min=0.0, min_included=False))
|
||||
|
||||
|
||||
def validate_adc_time(value: Any) -> int:
|
||||
value = cv.positive_time_period_microseconds(value).total_microseconds
|
||||
return cv.one_of(*ADC_TIMES_US, int=True)(value)
|
||||
|
||||
|
||||
def validate_alert_limits(config: ConfigType) -> ConfigType:
|
||||
shunt = config[CONF_SHUNT_RESISTANCE]
|
||||
for key in (CONF_WARNING_CURRENT_LIMIT, CONF_CRITICAL_CURRENT_LIMIT):
|
||||
if (limit := config.get(key)) is None:
|
||||
continue
|
||||
if limit * shunt > ALERT_LIMIT_MAX_V:
|
||||
raise cv.Invalid(
|
||||
f"{key} times shunt_resistance exceeds the "
|
||||
f"{ALERT_LIMIT_MAX_V * 1000:.1f} mV alert range",
|
||||
path=[key],
|
||||
)
|
||||
if alert_limit_register(limit, shunt) == 0:
|
||||
raise cv.Invalid(
|
||||
f"{key} times shunt_resistance is below the "
|
||||
f"{ALERT_LIMIT_LSB_V * 1e6:.0f} µV alert resolution",
|
||||
path=[key],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
INA3221_CHANNEL_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_BUS_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_SHUNT_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_CURRENT): CURRENT_SCHEMA,
|
||||
cv.Optional(CONF_POWER): POWER_SCHEMA,
|
||||
cv.Optional(CONF_SHUNT_RESISTANCE, default=0.1): cv.All(
|
||||
cv.resistance, cv.Range(min=0.0, max=32.0)
|
||||
),
|
||||
cv.Optional(CONF_WARNING_CURRENT_LIMIT): CURRENT_LIMIT_SCHEMA,
|
||||
cv.Optional(CONF_CRITICAL_CURRENT_LIMIT): CURRENT_LIMIT_SCHEMA,
|
||||
}
|
||||
),
|
||||
cv.has_at_least_one_key(
|
||||
CONF_BUS_VOLTAGE,
|
||||
CONF_SHUNT_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_POWER,
|
||||
CONF_WARNING_CURRENT_LIMIT,
|
||||
CONF_CRITICAL_CURRENT_LIMIT,
|
||||
),
|
||||
validate_alert_limits,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
def adc_times_us(config: ConfigType) -> tuple[int, int]:
|
||||
"""Return the bus and shunt conversion times in microseconds."""
|
||||
adc_time = config[CONF_ADC_TIME]
|
||||
if isinstance(adc_time, dict):
|
||||
return adc_time[CONF_BUS_VOLTAGE], adc_time[CONF_SHUNT_VOLTAGE]
|
||||
return adc_time, adc_time
|
||||
|
||||
|
||||
def single_shot_wait_ms(config: ConfigType) -> int:
|
||||
"""Time one conversion of every enabled channel takes, plus headroom."""
|
||||
bus_us, shunt_us = adc_times_us(config)
|
||||
channels = sum(1 for channel in CHANNELS if channel in config)
|
||||
total_us = (bus_us + shunt_us) * config[CONF_ADC_AVERAGING] * channels
|
||||
return -(-total_us // 1000) + SINGLE_SHOT_MARGIN_MS
|
||||
|
||||
|
||||
def validate_single_shot_interval(config: ConfigType) -> ConfigType:
|
||||
if config[CONF_CONTINUOUS_MODE]:
|
||||
return config
|
||||
wait_ms = single_shot_wait_ms(config)
|
||||
if config[CONF_UPDATE_INTERVAL].total_milliseconds <= wait_ms:
|
||||
raise cv.Invalid(
|
||||
f"A single-shot conversion of all channels takes {wait_ms} ms, which does "
|
||||
f"not fit in the update_interval; lower {CONF_ADC_AVERAGING} or "
|
||||
f"{CONF_ADC_TIME}, or raise the interval",
|
||||
path=[CONF_UPDATE_INTERVAL],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(INA3221Component),
|
||||
cv.Optional(CONF_CHANNEL_1): INA3221_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_CHANNEL_2): INA3221_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_CHANNEL_3): INA3221_CHANNEL_SCHEMA,
|
||||
cv.Optional(CONF_CONTINUOUS_MODE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_ADC_AVERAGING, default=1): cv.one_of(
|
||||
*ADC_AVERAGING, int=True
|
||||
),
|
||||
cv.Optional(CONF_ADC_TIME, default="8244us"): cv.Any(
|
||||
validate_adc_time,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_BUS_VOLTAGE): validate_adc_time,
|
||||
cv.Required(CONF_SHUNT_VOLTAGE): validate_adc_time,
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Optional(CONF_SUM_SHUNT_VOLTAGE): VOLTAGE_SCHEMA,
|
||||
cv.Optional(CONF_SUM_CURRENT): CURRENT_SCHEMA,
|
||||
cv.Optional(CONF_SUM_POWER): POWER_SCHEMA,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
.extend(i2c.i2c_device_schema(0x40)),
|
||||
cv.has_at_least_one_key(*CHANNELS),
|
||||
validate_single_shot_interval,
|
||||
)
|
||||
|
||||
|
||||
def alert_limit_register(current_a: float, shunt_ohm: float) -> int:
|
||||
return round(current_a * shunt_ohm / ALERT_LIMIT_LSB_V) << 3
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
for i, channel in enumerate([CONF_CHANNEL_1, CONF_CHANNEL_2, CONF_CHANNEL_3]):
|
||||
if channel not in config:
|
||||
bus_us, shunt_us = adc_times_us(config)
|
||||
register = (
|
||||
ADC_AVERAGING[config[CONF_ADC_AVERAGING]] << ADC_AVERAGING_SHIFT
|
||||
| ADC_TIMES_US[bus_us] << ADC_TIME_BUS_SHIFT
|
||||
| ADC_TIMES_US[shunt_us] << ADC_TIME_SHUNT_SHIFT
|
||||
| (MODE_CONTINUOUS if config[CONF_CONTINUOUS_MODE] else MODE_SINGLE_SHOT)
|
||||
)
|
||||
for i, channel in enumerate(CHANNELS):
|
||||
if (conf := config.get(channel)) is None:
|
||||
continue
|
||||
conf = config[channel]
|
||||
if CONF_SHUNT_RESISTANCE in conf:
|
||||
cg.add(var.set_shunt_resistance(i, conf[CONF_SHUNT_RESISTANCE]))
|
||||
register |= CHANNEL_ENABLE_BITS[i]
|
||||
shunt = conf[CONF_SHUNT_RESISTANCE]
|
||||
cg.add(var.set_shunt_resistance(i, shunt))
|
||||
if CONF_BUS_VOLTAGE in conf:
|
||||
sens = await sensor.new_sensor(conf[CONF_BUS_VOLTAGE])
|
||||
cg.add(var.set_bus_voltage_sensor(i, sens))
|
||||
@@ -98,3 +228,23 @@ async def to_code(config: ConfigType) -> None:
|
||||
if CONF_POWER in conf:
|
||||
sens = await sensor.new_sensor(conf[CONF_POWER])
|
||||
cg.add(var.set_power_sensor(i, sens))
|
||||
for key, setter in (
|
||||
(CONF_WARNING_CURRENT_LIMIT, var.set_warning_limit),
|
||||
(CONF_CRITICAL_CURRENT_LIMIT, var.set_critical_limit),
|
||||
):
|
||||
if (limit := conf.get(key)) is not None:
|
||||
cg.add_define("USE_INA3221_ALERT_LIMITS")
|
||||
cg.add(setter(i, alert_limit_register(limit, shunt)))
|
||||
cg.add(var.set_config_register(register))
|
||||
if not config[CONF_CONTINUOUS_MODE]:
|
||||
cg.add(var.set_single_shot_wait_ms(single_shot_wait_ms(config)))
|
||||
|
||||
if any(
|
||||
key in config
|
||||
for key in (CONF_SUM_SHUNT_VOLTAGE, CONF_SUM_CURRENT, CONF_SUM_POWER)
|
||||
):
|
||||
cg.add_define("USE_INA3221_SUMMATION")
|
||||
sensors = sensor.sub_sensors(config)
|
||||
await sensors(CONF_SUM_SHUNT_VOLTAGE, var.set_sum_shunt_voltage_sensor)
|
||||
await sensors(CONF_SUM_CURRENT, var.set_sum_current_sensor)
|
||||
await sensors(CONF_SUM_POWER, var.set_sum_power_sensor)
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
#define USE_I2S_AUDIO_SPDIF_MODE
|
||||
#define USE_IMAGE
|
||||
#define USE_IMPROV_BLE_STATE_CALLBACK
|
||||
#define USE_INA3221_ALERT_LIMITS
|
||||
#define USE_INA3221_SUMMATION
|
||||
#define USE_INFRARED
|
||||
#define USE_IR_RF
|
||||
#define USE_IR_RF_TRANSMIT_COMPLETE
|
||||
|
||||
@@ -13,3 +13,32 @@ sensor:
|
||||
shunt_voltage:
|
||||
name: INA3221 Channel 1 Shunt Voltage
|
||||
update_interval: 15s
|
||||
- platform: ina3221
|
||||
i2c_id: i2c_bus
|
||||
address: 0x41
|
||||
continuous_mode: false
|
||||
adc_averaging: 4
|
||||
adc_time:
|
||||
bus_voltage: 2116us
|
||||
shunt_voltage: 1.1ms
|
||||
channel_1:
|
||||
shunt_resistance: 0.05 ohm
|
||||
current:
|
||||
name: INA3221 B Channel 1 Current
|
||||
warning_current_limit: 1A
|
||||
critical_current_limit: 2A
|
||||
channel_2:
|
||||
shunt_resistance: 0.05 ohm
|
||||
bus_voltage:
|
||||
name: INA3221 B Channel 2 Bus Voltage
|
||||
channel_3:
|
||||
shunt_resistance: 0.05 ohm
|
||||
power:
|
||||
name: INA3221 B Channel 3 Power
|
||||
sum_shunt_voltage:
|
||||
name: INA3221 B Sum Shunt Voltage
|
||||
sum_current:
|
||||
name: INA3221 B Sum Current
|
||||
sum_power:
|
||||
name: INA3221 B Sum Power
|
||||
update_interval: 15s
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
ina3221: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
ina3221: !include common.yaml
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
ina3221: !include common.yaml
|
||||
|
||||
Reference in New Issue
Block a user