Battery powered ESP goes down way too fast

I'm a bit helpless. I now try for some time to run an ESP-12 and/or Wemos on battery for a pool thermometer. I want it to run over the whole season ideally. It's powered by a 3000mAh 18650 lico cells. Then an MCP1700 to output 3.3V for the ESP.
A voltage divider of 100k & 300k is used to connect battery raw voltage to A0.
A DS18B20 is connected to D1 (with the required 4.7k resistor over VCC/D1).

I have a high quality DMM to measure current, and I can confirm that I see around 80mA when the ESP is booting and running and around 80uA when in deep sleep. I know this is more then what can be expected, but i guess that's because of the voltage divider.

It's set to wake up every 10min. With ESPEASY it takes very long to obtain an IP, send the measurement to MQTT and power down again. So it's about 5s, that's around 30s every hour. And it will be consuming 0.08mA for 3600s (-30s).
So it consumes 30x80 + 3600x0.08 mA = 2688mAs or 0.747mAh. Correct? That would mean it could run 167days or around half a year.

I also wrote my own code where it uses fixed IP address, and doesn't go through the whole DHCP process. That way first boot takes 5s and the following ones are sub one second.

But reality proves me wrong. This is the graph for 24h. It loses 50mV PER DAY. So it would deplete within 4.20-3.00 = 1.20/0.05 = 24days, assuming linear depletion, which will not happen. This is for the ESP-12. Wemos of course is much worse with FTDI and DC regulator still connected.

The gurus here will chime in at any time. But one thing that makes me scratch my head is, why every 10 minutes? A swimming pool's temperature can't really change much in that short of a period. Unless you really have a need to see changes of a few tenths of a degree, I would consider changing to every hour.

I agree, I'm open for diskussion. But my main concern is that my calculated runtime is off by a factor of 10. I could set it to 1h and see what happens. But even with an "online" time of sub 1 second it doesn't change much.
Any people are talking about "runnning ESPs on battery FOR YEARS", and I'm so TOTALLY OFF, that there must be something very wrong, and i can't fing it now for weeks.

First place I’d look is at those “3000mAh 18650 lico cells”. A 3000mAh 18650 should be heavy with a fill of lithium and other things.

I found that when I bought those miracle mAh batteries at some incredible price… Buyer be ware.

Next, the ESP32 has 3 cores. All the modules of the ESP32 can be shut down, leaving just the RTC and ULP processor with power. The RTC can be set to wake up the ULP, the ULP do its thing, and either go back to sleep or wake up the rest of the ESP32 module to do the thing.

This is a link to the ESP32 API, https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/index.html that will have info on the ULP.

Deep Sleep Wake Stubs https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/deep-sleep-stub.html

ULP coprocessor (Legacy GNU Make) https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/ulp-legacy.html

The current Arduino IDE ESP32 core uses the Legacy GNU make format.

Here is an example to program the ULP to blink the on-board LED:

#include "esp32/ulp.h"
#include "driver/rtc_io.h"
void ULP_BLINK_RUN(uint32_t us);
setup()
{
 ULP_BLINK_RUN(100000);
}
void ULP_BLINK_RUN(uint32_t us)
{
  int memPortState = 8000; // memory address outside of program space to use
  int memCounts = 8001; // memory address to hold a count
  size_t load_addr = 0;
  RTC_SLOW_MEM[memPortState] = 0;
  RTC_SLOW_MEM[memCounts] = 0;
  ulp_set_wakeup_period(0, us);
  const ulp_insn_t  ulp_blink[] =
  {
    I_MOVI( R2, memCounts ), // get info from memCounts address
    I_LD( R1, R2, 0 ),       // put contents of memCounts into R1
    I_ADDI( R1, R1, 1 ),     // Add 1 to R1 holding result into R1
    I_ST( R1, R2, 0 ),       // Put R1 into mem address pointed to by R2
    I_MOVI(R3, memPortState),               // memPortState -> R3
    I_LD(R0, R3, 0),                        // R0 = RTC_SLOW_MEM[R3(memPortState)]
    M_BL(1, 1),                             // GOTO M_LABEL(1) IF R0 < 1
    I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 1),  // RTC_GPIO2 = 1
    I_SUBI(R0, R0, 1),                      // R0 = R0 - 1, R0 = 1, R0 = 0
    I_ST(R0, R3, 0),                        // RTC_SLOW_MEM[R3(memPortState)] = R0
    M_BX(2),                                // GOTO M_LABEL(2)
    M_LABEL(1),                             // M_LABEL(1)
    I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 0),  // RTC_GPIO2 = 0
    I_ADDI(R0, R0, 1),                      // R0 = R0 + 1, R0 = 0, R0 = 1
    I_ST(R0, R3, 0),                        // RTC_SLOW_MEM[R3(memPortState)] = R0
    M_LABEL(2),                             // M_LABEL(2)
    I_HALT()                                // HALT COPROCESSOR
  };
  rtc_gpio_init( GPIO_NUM_2 ); // GPIO2 built in led
  rtc_gpio_set_direction( GPIO_NUM_2, RTC_GPIO_MODE_INPUT_OUTPUT );
  rtc_gpio_set_level( GPIO_NUM_2, 0);
  size_t size = sizeof(ulp_blink) / sizeof(ulp_insn_t);
  ulp_process_macros_and_load( load_addr, ulp_blink, &size);
  ulp_run( load_addr );
} // void ULP_BLINK_RUN(uint32_t us)

It is up to you, the programmer to keep track of RTC memory used. To help keep track of memory used keep this in mind

/*
  Each I_XXX preprocessor define translates into a single 32-bit instruction. So you can count instructions to learn which memory address are used and where the free mem space starts.

  To generate branch instructions, special M_ preprocessor defines are used. M_LABEL define can be used to define a branch target.
  Implementation note: these M_ preprocessor defines will be translated into two ulp_insn_t values: one is a token value which contains label number, and the other is the actual instruction.
*/

RTC memory is 8K in size. Thus the memory location for the variable storage location can be 8000, which should give room for about 100 variables.


With that, I think the issue you are experiencing starts with the batteries and getting good quality, weighty, batteries.


You can put a FET from the bottom resistor of the V divider to ground and have the ULP enable/disable the FET during power up/down cycles.


Also, note how this project uses a LDO Power ESP32/ESP8266 with Solar Panels and Battery | Random Nerd Tutorials to keep the ESP32 powered for as long as possible as batt voltage drops.

The battery is not the problem for sure. It’s all genuine cells from nkon.nl. Tested by me as well. So it must be something else.

Here’s the sub second code, that wakes up, measures, sends to mqtt and goes to sleep in under a second.

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

/* Required for the DS18B20                  */
#include <OneWire.h>
#include <DallasTemperature.h>

/* Update these files with your details      */
#include "config.h"
#define ONE_WIRE_BUS D1

WiFiClient wifiClient;
PubSubClient client(wifiClient);

// Create a OneWire instance
OneWire oneWire(ONE_WIRE_BUS);

DallasTemperature sensors(&oneWire);

const int analogInPin = A0;
int sensorValue = 0;
float outputValue = 0;

int connect() {
 int result = 0;

 int counter = 0;

 Serial.print("Connecting to ");
 Serial.println(WIFI_SSID);

 /* Set to station mode and connect to network */
 WiFi.mode(WIFI_STA);
 WiFi.config(IPAddress(192, 168, 1, 75), IPAddress(192, 168, 1, 0), IPAddress(255, 255, 255, 0));
 WiFi.begin(WIFI_SSID, WIFI_PASSWD);

 /* Wait for connection */
 while ((WiFi.status() != WL_CONNECTED) && (counter < RETRY_COUNT)) {
   counter++;
   Serial.print(".");
   delay(300);
 }
 Serial.println("");

 /* Check if we obtained an IP address */
 if (WiFi.status() != WL_CONNECTED) {
   Serial.println("Failed to connect. Sleeping!");
   result = 0;
 } else {
   Serial.println("");
   Serial.println("WiFi connected");
   Serial.println("IP address: ");
   Serial.println(WiFi.localIP());
   result = 1;
 }

 return result;

}
void setup() {

 int result = 0;
 int counter = 0;

 /* Serial connection */
 Serial.begin(115200);

 /* Start Dallas library */
 sensors.begin();
 sensors.setResolution(11);
 Serial.println("");
 Serial.println("ESP-01 Temperature Sensor (MQTT)");
 Serial.println("");

 /* Get ChipID and convert to char array */
 char strChipID[33];
 itoa(ESP.getChipId(), strChipID, 10);

 /* Concatenate to form MQTT topic */
 String strTopic = MQTT_TOPIC_PREFIX;
 strTopic = strTopic + "ESP" + strChipID;

 /* Attempt to connect to WiFi */
 result = connect();

 //  if (result == 1) {
 {
   /* Read Analog Pin A0 */

   sensorValue = analogRead(analogInPin);
   outputValue = map(sensorValue, 0, 1024, 0, 4117); // calibrated
   outputValue = outputValue / 1000;

   char Voltage[7];
   dtostrf(outputValue, 6, 2, Voltage);
   char Sensor[7];
   dtostrf(sensorValue, 6, 0, Sensor);

   /* Read temps from all devices on one wire bus */
   sensors.requestTemperatures();

   /* Read temp from sensor as float.
      Convert into string of format "-##.##" */
   float fTempC = sensors.getTempCByIndex(0);
   fTempC = fTempC + 1.5; // Sensorkorrektur

   if (fTempC != DEVICE_DISCONNECTED_C) {
     /* Valid reading */
     char strTempC[7];
     dtostrf(fTempC, 6, 2, strTempC);

     /* Connect to MQTT broker */
     client.setServer(MQTT_SERVER, MQTT_PORT);

     if (client.connect(strChipID, MQTT_USER, MQTT_PASSWD))
     {
       Serial.print("Publishing ... ");
       Serial.print("Temperature: ");
       Serial.println(strTempC);
       client.publish("Wifi.Poolthermometer/Temp", strTempC);
       client.publish("Wifi.Poolthermometer/Volt", Voltage);
       client.publish("Wifi.Poolthermometer/Sensor", Sensor);
     }
     else {
       Serial.println("Error connecting to MQTT broker");
     }

     /* Close MQTT client cleanly */
     client.disconnect();

   }
 }
 /* Close WiFi connection */
 wifiClient.stop();

 /* Put device into deep sleep.
    Although without hardware mod it will never wake up from this! */
 Serial.println("Time to sleep!");
 Serial.print("Uptime:  ");
 long uptime = millis();
 Serial.println(uptime);
 ESP.deepSleep(DEVICE_SLEEP * 60 * 1000000);
 delay(100);

}
void loop() {
}

izeman:
But reality proves me wrong. This is the graph for 24h. It loses 50mV PER DAY. So it would deplete within 4.20-3.00 = 1.20/0.05 = 24days, assuming linear depletion, which will not happen.

Battery voltage and charge level are not linearly related. When a battery is used, at the beginning the voltage drops fast, then it stabilises for a long time until the battery is almost empty, after which it starts to drop fast again.

Just let it run for the time being, see what the actual lifetime is. And of course: make sure your batteries are really 3,000 mAh. The good ones are not cheap, expect the equivalent of USD 20 or so per battery for that capacity.

Battery voltage falls after charging anyway even if you draw no current, its completely not a guide to state of charge. "assuming linear depletion" - wrong assumption.

Discharge curves for a cell are anything but linear just after charging and when the cell is nearly empty.

You need to verify the capacity of the cells by charging and then discharging through a resistor at a rate that should take a day or so (careful never to take a cell below its minimum voltage - that's very damaging).

Then so long as your measurements and calculations are correct you should be OK - just remember to
allow a sizable safety factor as batteries capacity varies with temperature, age, rate of discharge and so forth.