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.