I’ve been setting up my house with a ton of sensors and have been having fun with Home Assistant recently. I typically prefer Power Over Etherenet(PoE) devices such as the Olimex ESP32-POE-ISO, but I wanted to experiment with a smaller form factor WiFi solution in the event that I need a wireless approach. I found the Adafruit Huzzah32 which is effectively an Adafruit ESP32 Feather with some nice modifications:

We packed everything you love about Feathers: built in USB-to-Serial converter, automatic bootloader reset, Lithium Ion/Polymer charger, and all the GPIO brought out so you can use it with any of our Feather Wings.

I want to use these devices for any purpose, but I also want to report back to HomeAssistant the battery voltage so that I can attempt to recharge them when they get low. There is no built-in ESPHome battery monitor, but it allows for reading ADC voltages directly.

I found this post from cuddletech that explained how to convert the ADC readings into voltage. In short:

When you read the ADC you’ll get a value like 2339. The ADC value is a 12-bit number, so the maximum value is 4095 (counting from 0). To convert the ADC integer value to a real voltage you’ll need to divide it by the maximum value of 4095, then double it (note above that Adafruit halves the voltage), then multiply that by the reference voltage of the ESP32 which is 3.3V and then vinally, multiply that again by the ADC Reference Voltage of 1100mV.

I was having problems with this calculation due to the fact that my readings using these calculations kept coming out > 7 Volts, so I decided to dive in to see what’s wrong.

The code on this page references mongoose-os adc. It turns out, on the implemenation of the ADC, it is assumed that the default attenuation value for pin 35 is 11db:

// ... (truncated)
// From https://github.com/mongoose-os-libs/adc/blob/b1d3bf6312d4c624314b6ca1dee1d4e722fe8417/src/esp32/esp32_adc.c#L40
static struct esp32_adc_channel_info s_chans[8] = {
    {.pin = 36, .ch = ADC1_CHANNEL_0, .atten = ADC_ATTEN_DB_11},
    {.pin = 37, .ch = ADC1_CHANNEL_1, .atten = ADC_ATTEN_DB_11},
    {.pin = 38, .ch = ADC1_CHANNEL_2, .atten = ADC_ATTEN_DB_11},
    {.pin = 39, .ch = ADC1_CHANNEL_3, .atten = ADC_ATTEN_DB_11},
    {.pin = 32, .ch = ADC1_CHANNEL_4, .atten = ADC_ATTEN_DB_11},
    {.pin = 33, .ch = ADC1_CHANNEL_5, .atten = ADC_ATTEN_DB_11},
    {.pin = 34, .ch = ADC1_CHANNEL_6, .atten = ADC_ATTEN_DB_11},
    {.pin = 35, .ch = ADC1_CHANNEL_7, .atten = ADC_ATTEN_DB_11},
};
// ... (truncated)

This means that we have to set the attenuation properly in ESPHome’s adc sensor in order to read the correct range of voltages:

// From https://github.com/esphome/esphome/blob/410fad3b41640b76c7f902fb4656d0b1c2598681/esphome/components/adc/adc_sensor.cpp#L22
#ifdef ARDUINO_ARCH_ESP32
  analogSetPinAttenuation(this->pin_, this->attenuation_);
#endif

Note that ESPHome automatically divides by 4095 and multiplies by the attenuation max value for us, so we’ll have to compensate in order to use cuddletech’s calculation.

// ... (truncated)

// From https://github.com/esphome/esphome/blob/410fad3b41640b76c7f902fb4656d0b1c2598681/esphome/components/adc/adc_sensor.cpp#L61
float value_v = analogRead(this->pin_) / 4095.0f;  // NOLINT

// ... (truncated)

// From https://github.com/esphome/esphome/blob/410fad3b41640b76c7f902fb4656d0b1c2598681/esphome/components/adc/adc_sensor.cpp#L73
case ADC_11db:
 value_v *= 3.9;
 break;

// ... (truncated)

Putting this all together, we can use the following ESPHome yaml configuration to define the calculation for battery voltage:

esphome:
  name: huzzah32_example
  platform: ESP32
  # No built in huzzah32 board, but it seems identical to the featheresp32.
  board: featheresp32

wifi:
  ssid: "myssid"
  password: "supersecretpassword"

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:

status_led:
  pin: LED

sensor:
# Documentation: https://esphome.io/components/sensor/adc.html
- platform: adc

  # https://learn.adafruit.com/adafruit-huzzah32-esp32-feather/power-management
  pin: A13

  name: "ESP32 Battery Voltage"

  update_interval: 10s

  # See https://murt.is/articles/2021-02/huzzah32-battery-monitoring-esphome.md
  attenuation: 11db

  # Calculation based on https://cuddletech.com/?p=1030, modified to account for
  # ESPHome's internal math
  # (https://github.com/esphome/esphome/blob/410fad3b41640b76c7f902fb4656d0b1c2598681/esphome/components/adc/adc_sensor.cpp#L59).
  # (x / 3.9) should be the adc measurement converted to Volts.
  filters:
    - lambda: return (x / 3.9) * 2 * 3.3 * 1.1;

And huzzah! We now have mathematically correct battery voltages streaming into HomeAssistant from the Huzzah32 board.

Home Assistant Battery Page

I’ll make a follow up post on calibrating the ADC readings and attempting to decrease power usage.