Adafruit MAX17048 LiPo battery gauge with ESPHome

Maybe someone can help, I’m struggling to get this right. I want to use Adafruit’s MAX17048 LiPo battery gauge:

I tried to take inspiration from this thread:

Ok so I tried modifying a-marcel’s code and got it to at least produce some output but the numbers seem wildly wrong:

[18:28:14][D][sensor:127]: 'Voltage': Sending state 3616.25000  with 0 decimals of accuracy
[18:28:14][D][sensor:127]: 'Percentage': Sending state 3.10937  with 0 decimals of accuracy

I confess that I really don’t understand what this is doing or why we need all these bit shift operators.

  void update() override {
    float voltage = 1.25f * (float)(read16(MAX17043_VCELL) >> 4);
    voltage_sensor->publish_state(voltage);

    uint16_t percentage_tmp = read16(MAX17043_SOC);
    float percentage = (float)((percentage_tmp >> 8) + 0.003906f * (percentage_tmp & 0x00ff));

    percentage_sensor->publish_state(percentage);
  }

Why all the complicated formulas? Does the sensor not produce a simple voltage number and percentage so the data has to be massaged like this?

Ok…so I think I have this solved but maybe someone else can check my work. I copied these formulas from the source code of the Adafruit driver:

So now my code looks like this:

  void update() override {
    //float voltage = 1.25f * (float)(read16(MAX17048_VCELL) >> 4);
    float voltage = (float)(read16(MAX17048_VCELL)) * 78.125 / 1000000;
    voltage_sensor->publish_state(voltage);

    uint16_t percentage_tmp = read16(MAX17048_SOC);
    //float percentage = (float)((percentage_tmp >> 8) + 0.003906f * (percentage_tmp & 0x00ff));
    float percentage = (float)(percentage_tmp) / 256;
    percentage_sensor->publish_state(percentage);
  }

Now it produces outputs that look like a voltage and percentage:

[06:24:58][D][sensor:127]: 'Voltage': Sending state 3.56000  with 0 decimals of accuracy
[06:24:58][D][sensor:127]: 'Percentage': Sending state 93.23047  with 0 decimals of accuracy

Does it look right to you? Am I missing anything?

The values are a bit odd, a fully charged battery started at 103% and 4.2V. Methinks my formulas aren’t quite optimized correctly.

image

but at least I can watch the values change as the battery discharges.

This is working great, if anyone else needs a solution for monitoring battery life. Here’s the full code in case anyone else needs it.

MAX17048_component.h - put this file in your esphome folder:

#include "esphome.h"

#define MAX17048_ADDRESS        0x36
#define MAX17048_VCELL          0x02 // voltage
#define MAX17048_SOC            0x04 // percentage
#define MAX17048_MODE           0x06
#define MAX17048_VERSION        0x08
#define MAX17048_CONFIG         0x0c
#define MAX17048_COMMAND        0xfe

class MAX17048Sensor : public PollingComponent, public Sensor {
 public:
  Sensor *voltage_sensor = new Sensor();
  Sensor *percentage_sensor = new Sensor();

  MAX17048Sensor() : PollingComponent(10000) {}

  void setup() override {
    // Initialize the device here. Usually Wire.begin() will be called in here,
    // though that call is unnecessary if you have an 'i2c:' entry in your config
    ESP_LOGD("custom", "Starting up MAX17048 sensor");

    Wire.begin();
  }

  uint16_t read16(uint8_t reg) {
      uint16_t temp;
      Wire.begin();
      Wire.beginTransmission(MAX17048_ADDRESS);
      Wire.write(reg);
      Wire.endTransmission();
      Wire.requestFrom(MAX17048_ADDRESS, 2);
      temp = (uint16_t)Wire.read() << 8;
      temp |= (uint16_t)Wire.read();
      Wire.endTransmission();
      return temp;
  }

  void update() override {
    float voltage = (float)(read16(MAX17048_VCELL)) * 78.125 / 1000000;
    voltage_sensor->publish_state(voltage);

    uint16_t percentage_tmp = read16(MAX17048_SOC);
    float percentage = (float)(percentage_tmp) / 256;
    percentage_sensor->publish_state(percentage);
  }
};

Put this in your ‘includes’:

esphome:
  includes:
    - MAX17048_component.h

and this in your sensor section:

  - platform: custom
    lambda: |-
      auto max17048_sensor = new MAX17048Sensor();
      App.register_component(max17048_sensor);
      return {max17048_sensor->voltage_sensor, max17048_sensor->percentage_sensor};
    sensors:
      - name: "Voltage"
        unit_of_measurement: V
        accuracy_decimals: 2
      - name: "Percentage"
        unit_of_measurement: '%'
4 Likes

Thanks, @greenleaf Appreciate you providing this code.

I tried it with my setup and the voltage is fine, but I need a way to adjust the percent range. In my case I’m using 3 AA batteries, so 100% is 4.75 V. Is there a place to adjust this in the code or is this hard coded into the sensor?

hmm, best guess would be to try tweaking this line and see if your readings line up closer to 4.75 on a full charge

    float voltage = (float)(read16(MAX17048_VCELL)) * 78.125 / 1000000;

Has anyone done anything similar for the LC709203F

Hello,

I trying to build this ESPHome 2024.3.0 and I am facing an issue. Is that the case for you too?

INFO ESPHome 2024.3.0
INFO Reading configuration /config/esphome/adafruit-qualia-esp32-s3.yaml...
INFO Generating C++ source...
INFO Compiling app...
Processing adafruit-qualia-esp32-s3 (board: esp32-s3-devkitc-1; framework: espidf; platform: platformio/[email protected])
--------------------------------------------------------------------------------
HARDWARE: ESP32S3 240MHz, 320KB RAM, 8MB Flash
 - framework-espidf @ 3.40406.240122 (4.4.6) 
 - tool-cmake @ 3.16.4 
 - tool-ninja @ 1.7.1 
 - toolchain-esp32ulp @ 2.35.0-20220830 
 - toolchain-riscv32-esp @ 8.4.0+2021r2-patch5 
 - toolchain-xtensa-esp32s3 @ 8.4.0+2021r2-patch5
Reading CMake configuration...
Dependency Graph
|-- noise-c @ 0.1.4
Compiling .pioenvs/adafruit-qualia-esp32-s3/src/main.o
/config/esphome/adafruit-qualia-esp32-s3.yaml: In lambda function:
/config/esphome/adafruit-qualia-esp32-s3.yaml:94:34: error: expected type-specifier before 'MAX17048Sensor'
       auto max17048_sensor = new MAX17048Sensor();
                                  ^~~~~~~~~~~~~~
/config/esphome/adafruit-qualia-esp32-s3.yaml:96:82: error: could not convert '{<expression error>, <expression error>}' from '<brace-enclosed initializer list>' to 'std::vector<esphome::sensor::Sensor*>'
       return {max17048_sensor->voltage_sensor, max17048_sensor->percentage_sensor};
                                                                                  ^
*** [.pioenvs/adafruit-qualia-esp32-s3/src/main.o] Error 1

When I try to use @greenleaf 's code I get the error: src/MAX17048_component.h:23:5: error: 'Wire' was not declared in this scope. Some poking around showed me that most ESPHome components are not using Wire. Any thoughts, anyone?

Apparently custom components like this one are now deprecated: Custom I²C Device — ESPHome
So maybe one of us will get a chance to create an external component for the max17048 and share it here!

1 Like

If anyone returns to this thread, I threw together a new version of the driver, which seems to work currently. I used it in this recipe:
https://community.home-assistant.io/t/recipe-esphome-adafruit-feather-esp32-s2-thinkink-e-paper-display-battery-monitor/

tl;dr:

external_components:
  - source: github://Option-Zero/esphome-components@max17048
    components: [max17048]

sensor:
  - platform: max17048
    battery_voltage:
      name: Battery voltage
    battery_level:
      name: Battery level
    rate:
      name: Battery discharge rate

Lmk if you have success with this @lboue .

4 Likes

This works perfectly, thanks! Do you have plans to make a PR and get it natively integrated on ESPHome?