Example: solar powered node, increase sleep time when battery drains

I have a solar node, where on cloudy days the battery is not charging enough. By default, this node sends its sensor data to home assistant and then sleeps for 5 minutes. When the battery voltage drops below 3.7V, the sleep time will linearly increase with the drop in voltage. At 3.0V the sleep time is 1 hour, and even more when the battery drops lower.

I am reading the battery voltage with an INA219 (which gives current and power consumption as well), but you can use any method to read the battery voltage. For example, using a voltage divider with resistors and then reading the voltage on one of the analog pins of the ESP.

In home assistant, I created a binary sensor “deep_sleep_activated”. By default the node is not using deep sleep, this prevents the node going to sleep when the sensor data is not yet read from home assistant. In my case, this works better than the other way around (a ‘prevent_deep_sleep’ sensor, like most examples show). I am using the esphome API, not MQTT.

When the node is not in deep sleep, it will ‘do stuff’ every minute and check the deep sleep time, and if it needs to be activated. Once deep sleep is activated the default 5-minute sleep interval will start (if the battery is fully charged).

Below is the code I created. It is not plug&play. I copied the relevant parts. Hopefully, it is useful for you, and if you have suggestions for improvement add them to the comments.

In the graph below the voltage (hour average) of the battery. The arrow shows the point where the increased sleep time is activated. The day before, the sleep time was always 5 minutes. You see the voltage drop gets less significant over time. (In my case my sensor is still consuming power when the esp in deep sleep)
Schermafbeelding 2022-10-18 om 10.27.08

esphome:
  name: 'Solarnode'
  # set CPU speed to 80Mhz (minimum needed for wifi) to save energy
  platformio_options:
    board_build.f_cpu: 80000000L
  on_boot:
    # wait until everything is initialized
    priority: -100.00
    then:
      - script.execute: do_stuff_and_sleep_if_sleepy

esp32:
  board: esp32dev
  framework:
    type: arduino  

# Enable logging
logger:
#   level: VERBOSE

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

ota:
  password: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  # connect to the first wifi router found to save some energy
  fast_connect: true
  # Set a static IP address to save some energy
  manual_ip:
    static_ip: x.x.x.x
    gateway: x.x.x.x
    subnet: x.x.x.x
  use_address: "x.x.x.x"

# for INA219
i2c:
  sda: GPIO21
  scl: GPIO22
  id: i2c_bus_a

time:
  - platform: homeassistant
    id: local_time

globals:
  - id: dynamic_sleep_duration
    type: int
    restore_value: yes
     # default sleep time
    initial_value: '300'

deep_sleep:
  # default sleep time
  sleep_duration: 5min
  id: deep_sleep_1

# if not in sleep, get data every minute
interval:
  - interval: 1min
    then:
      - script.execute: do_stuff_and_sleep_if_sleepy

binary_sensor:
  - platform: status
    name: "Solarnode Status"
  - platform: homeassistant
    id: deep_sleep_activated
    entity_id: input_boolean.deep_sleep_activated

sensor:
  - platform: template
    name: "Solarnode Sleep Time"
    id: solarnode_sleep_time
    unit_of_measurement: "s"
    icon: mdi:sleep
    accuracy_decimals: 0
    lambda: |-
      return id(dynamic_sleep_duration);

  - platform: ina219
    address: 0x40
    shunt_resistance: 0.1 ohm
    bus_voltage:
      name: "Solarnode Battery Bus Voltage"
      id: solarnode_battery_voltage
      on_value:
        then:
        - script.execute: calculate_sleep_time
    max_voltage: 26.0V
    max_current: 3.2A
    update_interval: 15s
    
    # other sensors

script:
  - id: do_stuff_and_sleep_if_sleepy
    then:
      # Do stuff here to get your sensor data.
      # For example a delay of 30s to allow esphome to send data to home assistant
      - delay: 30s
      - if:
          condition:
            - binary_sensor.is_on: deep_sleep_activated
          then:
            - logger.log:
                format: "Entering deep sleep for %d seconds..."
                args: [ 'id(dynamic_sleep_duration)' ]
            - lambda: |-  
                id(deep_sleep_1).set_sleep_duration(id(dynamic_sleep_duration)*1000);
            - deep_sleep.enter: deep_sleep_1
  - id: calculate_sleep_time
    then:
      - lambda: |-
          float VMAX = 3.7;
          float VMIN = 3.0;
          double MINIMUM_SLEEPTIME = 300;
          double SLEEPTIME_VMAX = 300;
          double SLEEPTIME_VMIN = 3600;
          float battery_voltage = id(solarnode_battery_voltage).state;
          id(dynamic_sleep_duration) = max(MINIMUM_SLEEPTIME, ceil(SLEEPTIME_VMIN - (SLEEPTIME_VMIN-SLEEPTIME_VMAX)*(battery_voltage-VMIN)/(VMAX-VMIN)));
          id(solarnode_sleep_time).publish_state(id(dynamic_sleep_duration));
          ESP_LOGD("main", "Sleeptime adjusted to: %d seconds (%.1f volt)", id(dynamic_sleep_duration), battery_voltage);

3 Likes

elegant solution for my eyes