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: '%'
1 Like

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