mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00: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:
		| @@ -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 | ||||||
		Reference in New Issue
	
	Block a user