How to restore template sensor on reboot?

I have a template sensor in ESPhome which I use as counter of the times a button has been pushed:

sensor:
  - platform: template
    name: "count since reboot"
    id: total_count
    accuracy_decimals: 0
    state_class: "total_increasing"

binary_sensor:
  - platform: gpio
    id: physical_button
    on_click:
      - sensor.template.publish:
        id: total_count
        state: !lambda |-
          if (isnan(id(total_count).state)) {
            return 1.0;
          } else {
            return id(total_count).state + 1.0;
          }

Since ESPhome can restore on reboot the status of a GPIO switch, can it also somehow restore a sensor value? I know that “total_increasing” will allow HA to understand there was a reset, but I would like to show in the sensor value the actual total value.

Store it in a Global ?

They can be restored.

It worked!
I also used “restore_from_flash”.

I’m glad ‘it’ worked, because I’m trying to do much the same.

I could never figure out why “restore_from_flash” did not work. I did not see any reference to indicating which states to store in the documentation (assumed all would be stored, never assume). But now there is hope.

However, I can’t figure out how to combine the global variable with the template sensor.

I would like to restore f.i. the value of this template sensor:

sensor:
  # Brine Fill Timer
  - platform: template
    name: "Water Softener Brine Fill Time"
    lambda: |-
      if (id(state).state == "Brine fill") {return (id(brine_time).state + 0.1);}
      else {return (id(brine_time).state);}
    update_interval: 6s 
    unit_of_measurement: "minutes"
    accuracy_decimals: 1
    id: brine_time

Could you share your updated code snipit?

This is my whole code, with private information obfuscated where needed.
It is used to have the smart plug run for a custom amount of time (it can be set from the web interface of home assistant) and then turn off if the power button is pressed shortly, or to turn permanently on if long pressed.
I use it to have a coffee grinder grind the right amount of coffee for a single cup…

# state and device classes:
# https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes

globals:
  - id: total_cups_int
    type: int
    restore_value: yes
# initial value to force-start at a certain value, not used after the first time, I think
#    initial_value: '1105'

substitutions:
  plug_name: SP111
  name: plug-sp111

  # Higher value gives lower watt readout
  current_res: "0.00217"
  # Lower value gives lower voltage readout
  voltage_div: "766"

esphome:
  name: ${name}
  platform: ESP8266
  board: esp8285
  esp8266_restore_from_flash: true

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect: off
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: !secret ap_ssid
    password: !secret ap_pass

captive_portal:

# Enable logging
logger:
  baud_rate: 0
#  level: VERY_VERBOSE

# Enable Home Assistant API
api:
#  password: "xxxxx"

ota:
  password: !secret ota_password

web_server:
#  port: 80
#  auth:
#    username: admin
#    password: !secret web_server_password

time:
  - platform: homeassistant
    id: homeassistant_time

sensor:
  - platform: wifi_signal
    name: "${plug_name} - WiFi"
    unit_of_measurement: "dB"
    update_interval: 60s
    device_class: "signal_strength"
    state_class: "measurement"
 
  - platform: uptime
    name: "${plug_name} - Uptime"
    icon: mdi:clock-outline
    update_interval: 60s
   
  - platform: hlw8012
    sel_pin:
      number: GPIO12
      inverted: True
    cf_pin: GPIO5
    cf1_pin: GPIO4
    current_resistor: ${current_res}
    voltage_divider: ${voltage_div}
    
# current not used because it involves the chip
# switching between voltage and current output, 
# but if needed current can be obtained from power anyway
#    current:
#      name: "${plug_name} - Ampere"
#      id: "${plug_name}_Current"
#      unit_of_measurement: A
#      accuracy_decimals: 3
#      icon: mdi:flash-outline
#      device_class: "current"
#      state_class: "measurement"
      
    voltage:
      name: "${plug_name} - Volt"
      id: "${plug_name}_Voltage"
      unit_of_measurement: V
      accuracy_decimals: 1
      icon: mdi:flash-outline
      device_class: "voltage"
      state_class: "measurement"
      
    power:
      name: "${plug_name} - Watt"
      unit_of_measurement: W
      id: "${plug_name}_Wattage"
      icon: mdi:flash-outline
      device_class: "power"
      state_class: "measurement"
      
    update_interval: 10s
    initial_mode: VOLTAGE
    change_mode_every: 4294967295
    
  - platform: total_daily_energy
    name: "${plug_name} - Daily consumption"
    power_id: "${plug_name}_Wattage"
    filters:
        # Multiplication factor from W to kW is 0.001
      - multiply: 0.001
    unit_of_measurement: kWh
    accuracy_decimals: 4
    icon: mdi:clock-alert  
    device_class: "energy"
    state_class: "total_increasing"

#  - platform: template
#    name: "${plug_name} - Power factor"
#    unit_of_measurement: "cos φ"
#    lambda: |- 
#      if (id(${plug_name}_Current).state > 0) {
#        return id(${plug_name}_Wattage).state / (id(${plug_name}_Voltage).state * id(${plug_name}_Current).state);
#      } else {
#        return 0.0;
#      }
#    device_class: "power_factor"
#    state_class: "measurement"
 
  - platform: template
    name: "Cups since reboot"
    id: total_cups
    accuracy_decimals: 0
#    entity_category: system
    state_class: "total_increasing"
    lambda: |-
      return id(total_cups_int);

status_led:
  pin:
    number: GPIO2
    inverted: True 
    
binary_sensor:
  - platform: gpio
    id: physical_button
    internal: true
    pin:
      number: GPIO13
    filters:
      - invert:
    on_click:
      - min_length: 40ms
        max_length: 1500ms
        then:
          - if:
              condition:
                switch.is_off: relay
              then:
                - button.press: one_cup
              else:
                - switch.turn_off: relay
      - min_length: 3000ms
        max_length: 10000ms
        then:
          - switch.toggle: relay

output:
  - platform: gpio
    pin: GPIO0
    id: led
    inverted: true

switch:
  - platform: gpio
    pin: GPIO15
    id: relay
    name: "${plug_name} - Switch"
    icon: mdi:power-socket-eu
    on_turn_on:
      - output.turn_on: led
    on_turn_off:
      - output.turn_off: led

number:
  - platform: template
    name: "Grinding time (s)"
    id: grind_time_float
    unit_of_measurement: "s"
    entity_category: config
    mode: box
    max_value: 30
    min_value: 5
    initial_value: 16
    step: 0.1
    optimistic: true

button:
  - platform: restart
    name: "${plug_name} - Restart"
    
  - platform: template
    name: "Grind for one cup"
    id: one_cup
    on_press:
      - switch.turn_on: relay
      - delay: !lambda "return id(grind_time_float).state * 1000 ;"
      - switch.turn_off: relay
      - lambda: |-
          id(total_cups_int) += 1;
      - sensor.template.publish:
          id: total_cups
          state: !lambda |-
            return id(total_cups_int);

Enjoy and don’t forget to reply if you got it to work or if you have troubles, so that other people can have a confirmation this template works.

Thank you for the reply and the code, I always learn new stuff from peoples code.

I did meanwhile figure it out, same, same. The relevant code from my module:

Set up the flash store and restore functionality:

# Enable restore from flash
esp8266:
  board: nodemcuv2
  restore_from_flash: true

# Store globals in flash every hour to survive restarts
preferences:
  flash_write_interval: 1h

Define the globals that will be stored/restored:

# Define global entities (save timers to survive reboot)

globals:

  # brine time
  - id: brine_time
    type: float
    restore_value: true

  # rinse time
  - id: rinse_time
    type: int
    restore_value: true

  # operational time
  - id: ops_time
    type: int
    restore_value: true

The globals are counters/timers:

# Define the timers

interval:

  # Brine fill timer
  - interval: 6s
    then:
      if:
        condition:
          - state_machine.state: "Brine fill"
        then:
          - lambda: id(brine_time) += 0.1;

  # Rinse timer
  - interval: 1min
    then:
      if:
        condition:
          - state_machine.state: "Rinse"
        then:
          - lambda: id(rinse_time) += 1;

  # Operational timer
  - interval: 1h
    then:
      if:
        condition:
          - state_machine.state: "Operational"
        then:
          - lambda: id(ops_time) += 1;

Make sensors out of the globals for use in HA:

sensor:

  # Brine Fill Time
  - platform: template
    name: "Water Softener Brine Fill Time"
    lambda: return id(brine_time);
    unit_of_measurement: "minutes"
    accuracy_decimals: 1

  # Rinse Time
  - platform: template
    name: "Water Softener Rinse Time"
    lambda: return id(rinse_time);
    unit_of_measurement: "minutes"
    accuracy_decimals: 0

  # Operational Time
  - platform: template
    name: "Water Softener Operational Time"
    lambda: return id(ops_time);
    unit_of_measurement: "hours"
    accuracy_decimals: 0

Full code for the water softener, I like the state machine functionality for ESPHome

# ======================
# WATER SOFTENER MONITOR
# ======================

# Basic configuration
esphome:
  name: "nodemcu-v2-03"
  friendly_name: "Water Softener Monitor"

# Set inital values for salt level
  on_boot:
    - lambda: id(salt_level_raw).publish_state(0);
    - lambda: id(salt_level).publish_state(60);

# Enable restore from flash
esp8266:
  board: nodemcuv2
  restore_from_flash: true

# Store globals in flash every hour to survive restarts
preferences:
  flash_write_interval: 1h

# Enable logging
logger:

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

ota:
  password: "..."

# Enable WiFi access and fallback hotspot (in case WiFi fails)
wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  ap:
    ssid: "Nodemcu-V2-03"
    password: !secret hotspot_password

captive_portal:

# Define global entities (save timers to survive reboot)

globals:

  # brine time
  - id: brine_time
    type: float
    restore_value: true

  # rinse time
  - id: rinse_time
    type: int
    restore_value: true

  # operational time
  - id: ops_time
    type: int
    restore_value: true


# Include ESPHome state machine

external_components:
  - source:
      type: git
      url: https://github.com/muxa/esphome-state-machine


# Define the state machine
 
state_machine:
  name: "Water Softener State Machine"
  states:
    - "Operational"
    - "Brine fill"
    - "Rinse"
    - "Reverse"
  inputs:
    - name: Brine fill start
      transitions:
        - Operational -> Brine fill
    - name: Rinse start
      transitions:
        - Brine fill -> Rinse
    - name: Reverse start
      transitions:
        - Rinse -> Reverse
    - name: Operational start
      transitions:
        - Reverse -> Operational
  initial_state: "Operational"


# Define the timers

interval:

  # Brine fill timer
  - interval: 6s
    then:
      if:
        condition:
          - state_machine.state: "Brine fill"
        then:
          - lambda: id(brine_time) += 0.1;

  # Rinse timer
  - interval: 1min
    then:
      if:
        condition:
          - state_machine.state: "Rinse"
        then:
          - lambda: id(rinse_time) += 1;

  # Operational timer
  - interval: 1h
    then:
      if:
        condition:
          - state_machine.state: "Operational"
        then:
          - lambda: id(ops_time) += 1;

# Switches

switch:
   
  # LED
  - platform: gpio
    pin:
      number: GPIO2
      inverted: yes
    name: "LED"
    icon: mdi:led-off
    id: led

  # Restart
  - platform: restart
    name: "Restart"


# Sensors

binary_sensor:

  # Valve 1 (connected to D6) Brine Fill
  - platform: gpio
    pin:
      number: GPIO12
      mode: INPUT_PULLUP
      inverted: yes
    name: "Water Softener Brine Valve"
    internal: no
    publish_initial_state: yes
    filters:
      delayed_off: 5s
    id: valve_1

    # Record state change and reset timers when regeneration starts
    on_press:
      if:
        condition:
          - state_machine.state: "Operational"
        then:
          - state_machine.transition: Brine fill start
          - lambda: id(brine_time) = 0;
          - lambda: id(rinse_time) = 0;
          - lambda: id(ops_time) = 0;
        else:
          - state_machine.transition: Reverse start

    # Record state change when regenerations stops
    on_release:
      if:
        condition:
          - state_machine.state: "Reverse"
        then:
          - state_machine.transition: Operational start

  # Valve 2 (connected to D5) Rinse
  - platform: gpio
    pin:
      number: GPIO14
      mode: INPUT_PULLUP
      inverted: yes
    name: "Water Softener Rinse Valve"
    internal: no
    publish_initial_state: yes
    filters:
      delayed_off: 5s
    id: valve_2

    # Record state change when rince cycle starts
    on_press:
      state_machine.transition: Rinse start

    # Record state change when rince cycle stops
    on_release:
      state_machine.transition: Reverse start


  # Salt Level Low Warning (< 10%)
  - platform: template
    name: "Water Softener Salt Level Low Warning"
    lambda: return (id(salt_level_percent).state < 10);
    id: salt_low


  # WiFi connection status
  - platform: status
    name: "Status"
    on_state:
      then:
        - switch.turn_on: led
        - delay: 10 min
        - switch.turn_off: led

sensor:

  # Brine Fill Time
  - platform: template
    name: "Water Softener Brine Fill Time"
    lambda: return id(brine_time);
    unit_of_measurement: "minutes"
    accuracy_decimals: 1

  # Rinse Time
  - platform: template
    name: "Water Softener Rinse Time"
    lambda: return id(rinse_time);
    unit_of_measurement: "minutes"
    accuracy_decimals: 0

  # Operational Time
  - platform: template
    name: "Water Softener Operational Time"
    lambda: return id(ops_time);
    unit_of_measurement: "hours"
    accuracy_decimals: 0


  # Salt Level Raw - effective tank height (50 cm) minus the distance between the sensor and the salt in centimeters
  - platform: ultrasonic
    name: "Water Softener Salt Level Raw"
    trigger_pin: D1
    echo_pin: D2
    pulse_time: 10ms
    update_interval: 1h
    filters:
      - lambda: return (0.50 - x) * 100;
    unit_of_measurement: "cm"
    accuracy_decimals: 0
    icon: mdi:arrow-expand-vertical
    id: salt_level_raw

  # Salt Level Filtered - median over 3 hours
  - platform: template
    name: "Water Sofetener Salt Level Filtered"
    lambda: return id(salt_level_raw).state;
    filters:
      - median:
          window_size: 24
          send_every: 4
          send_first_at: 4
    unit_of_measurement: "cm"
    accuracy_decimals: 0
    icon: mdi:arrow-expand-vertical
    id: salt_level_filtered

  # Salt Level - value can only decrease, must be reset after filling
  - platform: template
    name: "Water Softener Salt Level"
    lambda: return min(id(salt_level).state, id(salt_level_filtered).state);
    unit_of_measurement: "cm"
    accuracy_decimals: 0
    icon: mdi:arrow-expand-vertical
    id: salt_level
  
  # Salt Level Percent, full is about 40 cm, rounded to the closest 1%, limited to 0% .. 100%
  - platform: template
    name: "Water Softener Salt Level Percent"
    lambda: return id(salt_level).state;
    filters:
      - calibrate_linear:
          datapoints:
            - 0.0 -> 0.0
            - 50.0 -> 100.0
      - clamp:
          min_value: 0.0
          max_value: 100.0
    unit_of_measurement: "%"
    accuracy_decimals: 0
    icon: mdi:arrow-expand-vertical
    id: salt_level_percent

text_sensor:

  # Water Softener State Machine State
  - platform: state_machine
    name: "Water Softener State"
    id: state

# END

# Notes:
#
# A regeneration cycle starts with the brine fill valve opening to add water to the brine tank.
# The brine fill valve closes again. The brine fill time depends on the water used since the previous regeneration.
#
# Next the rinse valve opens which bypasses the softener for water use, and reverse flows rinse water through the softener.
# The reverse flow also sucks (venturi) the brine from the brine tank until empty.
# The rinse valve closes again. The rinse time also depends on the water used since the previous regeneration.
#
# Last the brine file valve opens again to put the sofetener back into normal operation.
# The brine fill valve close again. The opening time is 30 seconds.
#
# A regeneration is started after a certain volume of water is used or after maximum 96 hours (3 days).
# A regeneration starts between 3:00 and 4:00 in the morning.

That’s pretty cool. Thanks.
Oh, by the way. If you can’t be a decent human being and share your passwords or wifi info with everyone then the least you could do is post your banking, SSN and all that no big deal information instead.

You are welcome to lead the way