mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Add support for Analog Devices MAX17043 battery fuel gauge (#7522)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
		| @@ -237,6 +237,7 @@ esphome/components/ltr_als_ps/* @latonita | |||||||
| esphome/components/lvgl/* @clydebarrow | esphome/components/lvgl/* @clydebarrow | ||||||
| esphome/components/m5stack_8angle/* @rnauber | esphome/components/m5stack_8angle/* @rnauber | ||||||
| esphome/components/matrix_keypad/* @ssieb | esphome/components/matrix_keypad/* @ssieb | ||||||
|  | esphome/components/max17043/* @blacknell | ||||||
| esphome/components/max31865/* @DAVe3283 | esphome/components/max31865/* @DAVe3283 | ||||||
| esphome/components/max44009/* @berfenger | esphome/components/max44009/* @berfenger | ||||||
| esphome/components/max6956/* @looping40 | esphome/components/max6956/* @looping40 | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								esphome/components/max17043/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/max17043/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | CODEOWNERS = ["@blacknell"] | ||||||
							
								
								
									
										20
									
								
								esphome/components/max17043/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								esphome/components/max17043/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | |||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "max17043.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace max17043 { | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SleepAction : public Action<Ts...> { | ||||||
|  |  public: | ||||||
|  |   explicit SleepAction(MAX17043Component *max17043) : max17043_(max17043) {} | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { this->max17043_->sleep_mode(); } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   MAX17043Component *max17043_; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace max17043 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										98
									
								
								esphome/components/max17043/max17043.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								esphome/components/max17043/max17043.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,98 @@ | |||||||
|  | #include "max17043.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace max17043 { | ||||||
|  |  | ||||||
|  | // MAX174043 is a 1-Cell Fuel Gauge with ModelGauge and Low-Battery Alert | ||||||
|  | // Consult the datasheet at https://www.analog.com/en/products/max17043.html | ||||||
|  |  | ||||||
|  | static const char *const TAG = "max17043"; | ||||||
|  |  | ||||||
|  | static const uint8_t MAX17043_VCELL = 0x02; | ||||||
|  | static const uint8_t MAX17043_SOC = 0x04; | ||||||
|  | static const uint8_t MAX17043_CONFIG = 0x0c; | ||||||
|  |  | ||||||
|  | static const uint16_t MAX17043_CONFIG_POWER_UP_DEFAULT = 0x971C; | ||||||
|  | static const uint16_t MAX17043_CONFIG_SAFE_MASK = 0xFF1F;  // mask out sleep bit (7), unused bit (6) and alert bit (4) | ||||||
|  | static const uint16_t MAX17043_CONFIG_SLEEP_MASK = 0x0080; | ||||||
|  |  | ||||||
|  | void MAX17043Component::update() { | ||||||
|  |   uint16_t raw_voltage, raw_percent; | ||||||
|  |  | ||||||
|  |   if (this->voltage_sensor_ != nullptr) { | ||||||
|  |     if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) { | ||||||
|  |       this->status_set_warning("Unable to read MAX17043_VCELL"); | ||||||
|  |     } else { | ||||||
|  |       float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0; | ||||||
|  |       this->voltage_sensor_->publish_state(voltage); | ||||||
|  |       this->status_clear_warning(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (this->battery_remaining_sensor_ != nullptr) { | ||||||
|  |     if (!this->read_byte_16(MAX17043_SOC, &raw_percent)) { | ||||||
|  |       this->status_set_warning("Unable to read MAX17043_SOC"); | ||||||
|  |     } else { | ||||||
|  |       float percent = (float) ((raw_percent >> 8) + 0.003906f * (raw_percent & 0x00ff)); | ||||||
|  |       this->battery_remaining_sensor_->publish_state(percent); | ||||||
|  |       this->status_clear_warning(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MAX17043Component::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Setting up MAX17043..."); | ||||||
|  |  | ||||||
|  |   uint16_t config_reg; | ||||||
|  |   if (this->write(&MAX17043_CONFIG, 1) != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->read(reinterpret_cast<uint8_t *>(&config_reg), 2) != i2c::ERROR_OK) { | ||||||
|  |     this->status_set_warning(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   config_reg = i2c::i2ctohs(config_reg) & MAX17043_CONFIG_SAFE_MASK; | ||||||
|  |   ESP_LOGV(TAG, "MAX17043 CONFIG register reads 0x%X", config_reg); | ||||||
|  |  | ||||||
|  |   if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) { | ||||||
|  |     ESP_LOGE(TAG, "Device does not appear to be a MAX17043"); | ||||||
|  |     this->status_set_error("unrecognised"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // need to write back to config register to reset the sleep bit | ||||||
|  |   if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) { | ||||||
|  |     this->status_set_error("sleep reset failed"); | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void MAX17043Component::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "MAX17043:"); | ||||||
|  |   LOG_I2C_DEVICE(this); | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Communication with MAX17043 failed"); | ||||||
|  |   } | ||||||
|  |   LOG_UPDATE_INTERVAL(this); | ||||||
|  |   LOG_SENSOR("  ", "Battery Voltage", this->voltage_sensor_); | ||||||
|  |   LOG_SENSOR("  ", "Battery Level", this->battery_remaining_sensor_); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | float MAX17043Component::get_setup_priority() const { return setup_priority::DATA; } | ||||||
|  |  | ||||||
|  | void MAX17043Component::sleep_mode() { | ||||||
|  |   if (!this->is_failed()) { | ||||||
|  |     if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT | MAX17043_CONFIG_SLEEP_MASK)) { | ||||||
|  |       ESP_LOGW(TAG, "Unable to write the sleep bit to config register"); | ||||||
|  |       this->status_set_warning(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace max17043 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										29
									
								
								esphome/components/max17043/max17043.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								esphome/components/max17043/max17043.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sensor/sensor.h" | ||||||
|  | #include "esphome/components/i2c/i2c.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace max17043 { | ||||||
|  |  | ||||||
|  | class MAX17043Component : public PollingComponent, public i2c::I2CDevice { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   float get_setup_priority() const override; | ||||||
|  |   void update() override; | ||||||
|  |   void sleep_mode(); | ||||||
|  |  | ||||||
|  |   void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; } | ||||||
|  |   void set_battery_remaining_sensor(sensor::Sensor *battery_remaining_sensor) { | ||||||
|  |     battery_remaining_sensor_ = battery_remaining_sensor; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   sensor::Sensor *voltage_sensor_{nullptr}; | ||||||
|  |   sensor::Sensor *battery_remaining_sensor_{nullptr}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace max17043 | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										77
									
								
								esphome/components/max17043/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								esphome/components/max17043/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | |||||||
|  | from esphome import automation | ||||||
|  | from esphome.automation import maybe_simple_id | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import i2c, sensor | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_BATTERY_LEVEL, | ||||||
|  |     CONF_BATTERY_VOLTAGE, | ||||||
|  |     CONF_ID, | ||||||
|  |     DEVICE_CLASS_BATTERY, | ||||||
|  |     DEVICE_CLASS_VOLTAGE, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |     STATE_CLASS_MEASUREMENT, | ||||||
|  |     UNIT_PERCENT, | ||||||
|  |     UNIT_VOLT, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | DEPENDENCIES = ["i2c"] | ||||||
|  |  | ||||||
|  | max17043_ns = cg.esphome_ns.namespace("max17043") | ||||||
|  | MAX17043Component = max17043_ns.class_( | ||||||
|  |     "MAX17043Component", cg.PollingComponent, i2c.I2CDevice | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | # Actions | ||||||
|  | SleepAction = max17043_ns.class_("SleepAction", automation.Action) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(MAX17043Component), | ||||||
|  |             cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_VOLT, | ||||||
|  |                 accuracy_decimals=3, | ||||||
|  |                 device_class=DEVICE_CLASS_VOLTAGE, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema( | ||||||
|  |                 unit_of_measurement=UNIT_PERCENT, | ||||||
|  |                 accuracy_decimals=3, | ||||||
|  |                 device_class=DEVICE_CLASS_BATTERY, | ||||||
|  |                 state_class=STATE_CLASS_MEASUREMENT, | ||||||
|  |                 entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  |             ), | ||||||
|  |         } | ||||||
|  |     ) | ||||||
|  |     .extend(cv.polling_component_schema("60s")) | ||||||
|  |     .extend(i2c.i2c_device_schema(0x36)) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await i2c.register_i2c_device(var, config) | ||||||
|  |  | ||||||
|  |     if voltage_config := config.get(CONF_BATTERY_VOLTAGE): | ||||||
|  |         sens = await sensor.new_sensor(voltage_config) | ||||||
|  |         cg.add(var.set_voltage_sensor(sens)) | ||||||
|  |  | ||||||
|  |     if CONF_BATTERY_LEVEL in config: | ||||||
|  |         sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL]) | ||||||
|  |         cg.add(var.set_battery_remaining_sensor(sens)) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | MAX17043_ACTION_SCHEMA = maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.Required(CONF_ID): cv.use_id(MAX17043Component), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action("max17043.sleep_mode", SleepAction, MAX17043_ACTION_SCHEMA) | ||||||
|  | async def max17043_sleep_mode_to_code(config, action_id, template_arg, args): | ||||||
|  |     paren = await cg.get_variable(config[CONF_ID]) | ||||||
|  |     return cg.new_Pvariable(action_id, template_arg, paren) | ||||||
							
								
								
									
										19
									
								
								tests/components/max17043/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								tests/components/max17043/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | esphome: | ||||||
|  |   on_boot: | ||||||
|  |     then: | ||||||
|  |       - max17043.sleep_mode: max17043_id | ||||||
|  |  | ||||||
|  | i2c: | ||||||
|  |   - id: i2c_id | ||||||
|  |     scl: ${scl_pin} | ||||||
|  |     sda: ${sda_pin} | ||||||
|  |  | ||||||
|  | sensor: | ||||||
|  |   - platform: max17043 | ||||||
|  |     id: max17043_id | ||||||
|  |     i2c_id: i2c_id | ||||||
|  |     battery_voltage: | ||||||
|  |       name: "Battery Voltage" | ||||||
|  |     battery_level: | ||||||
|  |       name: Battery | ||||||
|  |     update_interval: 10s | ||||||
							
								
								
									
										6
									
								
								tests/components/max17043/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								tests/components/max17043/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO21 | ||||||
|  |   scl_pin: GPIO22 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								tests/components/max17043/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/max17043/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO8 | ||||||
|  |   scl_pin: GPIO10 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/max17043/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/max17043/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO8 | ||||||
|  |   scl_pin: GPIO10 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/max17043/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/max17043/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO21 | ||||||
|  |   scl_pin: GPIO22 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/max17043/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/max17043/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO4 | ||||||
|  |   scl_pin: GPIO5 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										5
									
								
								tests/components/max17043/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/max17043/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | substitutions: | ||||||
|  |   sda_pin: GPIO21 | ||||||
|  |   scl_pin: GPIO22 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
		Reference in New Issue
	
	Block a user