Please bear with me because I’m a retired mechanical engineer, fast approaching my 70th birthday and struggling to keep up with this sort of tech. What I’m trying to do is (within ESPHome) turn on a GPIO at a repeatable interval where the interval is configurable via a home assistant input number. Once turned on, then the GPIO needs to stay on for a period of time depending on the value of a second home assistant input number, then turn off. I’ve created the input numbers and also created “internal” global variables which have an initial value but which also get updated when the HA input number changes. But I can’t fathom out how to configure an automation within ESPHome where both the interval and duration are based on the value of the variables rather than fixed numbers. I thought I could use “interval.interval” but it seems that the it’s not templatable and will only take fixed numbers. I think I could write a script which would turn on the GPIO, delay for “n” *1000 ms, then turn off. Might that work? If so, I’d appreciate a bit more guidance as to how to write the script. But I’m still stuck with how to set a repeatable time interval for starting that script, based on the value of a global variable. Any help would be very much appreciated.
You can use a template trigger with an input_number helper and a modulo like this
{{ (now().timestamp()
| timestamp_custom('%M') | int) % n }}
Time trigger are documented here
You can also use this logic in esphome
In lambdas is may look like this
auto time = id(sntp_time).now();
If (time.minute%n == 0)
{ Logic here }
Thanks, but I am trying to find a solution that will run entirely on the ESP32 device. If I understand what you suggesting correctly, then if the WiFi connection failed for any reason, the automation would fail would it not? I’m not too worried about the actual time of day that the automation runs, just the period of time between.
Best practice for accurately timed events on an ESP32 - where you want it to run standalone and sometimes without internet connection, is to use a RTC.
ESPHome supports the DS1307 and the PFC85063 RTCs. Both have on-board independent batteries and keep accurate time for years (according to the specs ). Both are pretty cheap. The clock will then keep the time a lot more accurately that the ESP is able to do on its own (not that well, seconds out a day).
You can add an internet time source like SNTP that will update the clock when available, you must be careful however in making sure the network connection timeout is disables so that ESPHome doesn’t reboot when no network connection.
Once you have an accurate time source use the various on_time:
triggers documented in the Time Component linked above.
time:
- platform: sntp
# ...
on_time:
# Every 5 minutes
- seconds: 0
minutes: /5
then:
- switch.toggle: my_switch
# Every morning on weekdays
- seconds: 0
minutes: 30
hours: 7
days_of_week: MON-FRI
then:
- light.turn_on: my_light
# Cron syntax, trigger every 5 minutes
- cron: '* /5 * * * *'
then:
Your main issue is that the time interval for on_time:
is not templatable. So you can’t use a value from HA to set the interval. You need to use something like Benoît’s lambda code above and check for the interval being passed in an on_time:
block that runs every second. Very messy unfortunately.
Yes, not being able to template the on_time or interval depending an HA value is a bit of a pain. The use case for this is a kind of hybrid hydroponic/aeroponic system whereby a pump supplies nutrients to the plants for a period time, then turns off allowing the plant roots to absorb oxygen without drying out before the cycle repeats. To save energy costs, I want to keep the pump run-time to a minimum. So I’ll need to play around with the frequency and duration to find the minimum pump time usage without the plant roots drying out ( which might vary with things like ambient temperature etc).
But the times need not be too accurate - a few seconds here or there won’t matter much. So my next thought is maybe to use an external pulse generator set to say 1hz or thereabouts and feed the output from that into a gpio and use it to count “ticks”. Then maybe I could use this as a counter (or two) that I could compare with the HA input number sensor(s) and take the necessary action accordingly. What do you all think? Viable?
…on the other hand. After more research, and as far as I tell, “delay” can be templated and I believe the result of the return is ms. So, I’ve written a little script as follows:-
script:
- id: cycle_pump
then:
- delay: !lambda return (id:pump_interval-id:pump_on_time)*1000
- switch.turn_on: pump_relay
- delay: !lambda return id:pump_on_time*1000
- switch.turn_off: pump_relay
The pump_interval and pump_on_time are global variable which are given initial values but updated when HA sensors are available or change.
So, if this works, it should delay for the interval time minus the run time in ms, then turn on the pump, then delay for the run time in ms, then turn the pump off. So the interval between one pump turn on event and the next should equal my set value.
In theory, I think I could call that script from within a while loop (the “while” being a boolean state which will enable/disable the pump cycle) and trigger it on boot or state change or some such.
Thoughts? Have I constructed the script correctly? Are there any “Gotchas”?
You can also use ESP32 with a custom firmware (Arduino IDE) and make it communicate with HA through MQTT, you can even make it discoverable.
It’s easier if you have a custom project which needs lots of logic
Thanks but that’s probably a bit too advanced for an old man like me
So my next thought is maybe to use an external pulse generator set to say 1hz or thereabouts and feed the output from that into a gpio and use it to count “ticks”
That’s why RTC clock was suggested.
An RTC is basically a quartz, a battery, a register and something that count pulses (the battery is here to keep it running in case of power loss)
Also have you looked on the DIY websites if someone have done something similar or even here in Share your Projects! ?
Running a pump for gardening or for swimming pool is something frequently needed, so there’s chances that someone made something relevant to your needs
- delay: !lambda return (id:pump_interval-id:pump_on_time)*1000
I’m not sure if you’ll need to subtract the running time, because if your logic sequential the running time of the pump would already be counted on the first iterations.
Thus if interval is five minutes and running time five minutes it will run continuously since 5-5=0
It depends if you want to count from start point to start point or from stop to the next start
Hi @deckingman,
Another idea, have a look at “Slow PWM output” in ESPhome docs. I’ve never tried it myself, so can’t speak from personal experience, but it looks like it might suit your use case?
Andrew
Yes, there are different ways of looking at it. I want to be able to vary the run time independently of the total cycle time. Effectively increasing the on time will automatically reduce the off time. So the interval between cycles is from start to start. The allowable range for runtime is 60 to 300 seconds (1 to 5 minutes), and the allowable range for the cycle time is 360 to 900 seconds (6 to 15 minutes). So there will always be at least 1 minute of “off time”. I may need to adjust those parameters once the system is up and running.
Edit. Oh and I’ve spent days looking at other similar projects and ideas but nothing quite fits what I want to do.
Well I think I might be getting somewhere. I call the script on boot using a while loop but had a nasty feeling that if I change the boolean to disable the pump, then when I re-enable it, the while loop wouldn’t restart. So I’ve added while loops to all of the sensor such that if a state or value changes, it will restart. I’m sure there must be a more elegant way of achieving that. Anyway, it validates so looks like there might not be any errors - at least in my syntax. This thing also has a flow type level sensor which changes resistance as the float moves up or down, so I’ve created a couple of sensors to report the tank level as a percentage full and in litres. For the sake of completeness, the full config is below. I’d appreciate it if you knowledgeable people could take a gander to see if there are any glaringly obvious faults.
EDIT - THE FOLLOWING CODE IS FULL OF BUGS (THAT I INTRODUCED) - DO NOT USE - SEE LATER POST
esphome:
name: hydrotower1
friendly_name: HydroTower1
on_boot:
then:
- while:
condition:
lambda: |-
return id(pump_enabled).state = true
then:
- script.execute: cycle_pump
esp32:
board: esp32dev
framework:
type: arduino
globals:
- id: pump_enabled
type: bool
restore_value: yes
initial_value: 'true'
- id: pump_interval
type: float
restore_value: yes
initial_value: '360'
- id: pump_on_time
type: float
restore_value: yes
initial_value: '60'
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "this has been redacted........."
ota:
password: " .........as has this"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# manual_ip:
# static_ip: 192.168.1.80
# gateway: 192.168.1.1
# subnet: 255.255.255.0
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "redacted"
password: "redacted"
captive_portal:
switch:
- platform: gpio
pin: GPIO14
#inverted: true
name: "Pump Relay"
id: pump_relay
binary_sensor:
- platform: homeassistant
name: "Hydro Pump Enable"
entity_id: input_boolean.hydro_pump_enable
on_state:
then:
- lambda: |-
id(pump_enabled) = x;
- while:
condition:
lambda: |-
return id(pump_enabled).state = true
then:
- script.execute: cycle_pump
sensor:
- platform: homeassistant
name: Hydro Pump Interval
entity_id: input_number.hydro_pump_interval
on_value:
then:
- lambda: |-
id(pump_interval) = x;
- while:
condition:
lambda: |-
return id(pump_enabled).state = true
then:
- script.execute: cycle_pump
- platform: homeassistant
name: Hydro Pump On Time
entity_id: input_number.hydro_pump_on_time
on_value:
then:
- lambda: |-
id(pump_on_time) = x;
- while:
condition:
lambda: |-
return id(pump_enabled).state = true
then:
- script.execute: cycle_pump
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Raw"
id: "tank_level_raw"
icon: mdi:car-coolant-level
# change this to (say) 30 secs after testing
update_interval: 1s
# change to true after testing
internal: False
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Percent"
id: "tank_level_percent"
icon: mdi:car-coolant-level
# change this to (say) 30 secs after testing
update_interval: 1s
internal: False
# check calibration with actual voltage values
filters:
- calibrate_linear:
- 0.0 -> 0
- 0.95 -> 100
unit_of_measurement: "%"
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Litres"
id: "tank_level_litres"
icon: mdi:car-coolant-level
# chnage this to say 30 secs after testing
update_interval: 1s
internal: False
# max is 57 litres at end of float ravel, 62 litres to brim so say 60 lites max
# offset is 15 litres before float moves
# so with these filters, 0 volts should show 15 litres, 0.95 volts should show 45+15 = 60 litres
filters:
- calibrate_linear:
- 0.0 -> 0
- 0.95 -> 45
- offset: 15
unit_of_measurement: "Litres"
script:
- id: cycle_pump
then:
- delay: !lambda return (id:pump_interval-id:pump_on_time)*1000
- switch.turn_on: pump_relay
- delay: !lambda return id:pump_on_time*1000
- switch.turn_off: pump_relay
Hello fellow ME! I went down exactly that path (scripts and delays using HA inputs) while building a coffee roaster. I found a lot of the info out there completely confusing for a non-programmer such as myself and initially tried things that were WAY more complicated and unnecessary for my use-case (although maybe they would have been more elegant?). Once I started down this path, I spent most of my time troubleshooting YAML and lambda syntax issues rather than any of the actual logic, etc.
Here’s my (admittedly long) YAML file. Hopefully you can find it helpful?
substitutions:
friendly_name: Coffee Roaster
esphome:
name: coffee-roaster
on_boot:
priority: 500
then:
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- text_sensor.template.publish:
id: roast_status
state: "On"
- light.turn_off:
id: neopixel_light
- delay: 5s
- light.turn_off:
id: neopixel_light
- delay: 5s
- light.turn_off:
id: neopixel_light
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
# Enable Home Assistant API. Changed reboot_timeout to 0s to keep it from rebooting every 15min when using away from home.
api:
reboot_timeout: 0s
ota:
password: "xxxxxxxxxxxx"
wifi:
networks:
- ssid: !secret wifi_ssid
password: !secret wifi_password
- ssid: !secret trailer_wifi_ssid
password: !secret wifi_password
- ssid: !secret chucks_wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Coffee-Roaster Fallback Hotspot"
password: !secret wifi_password
#Removed so that when it can't connect to WiFi, the local AP it creates is for the web server and not setting up a different WiFi connection (i.e. allows the web server to be accessed without a WiFi network)
#captive_portal:
web_server:
port: 80
local: true
interval:
- interval: 1s
then:
#Safety check to make sure that if the heater is on, the fan is also on
- if:
condition:
and:
- lambda: 'return id(heat_value).state > 0;'
- fan.is_off: fan_speed
then:
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Heater On, Fan Not On"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
#Safety check to make sure that if the heater is 100% on for a while, the temperature actually increases.
- if:
condition:
and:
- lambda: 'return id(heat_value).state == 100;'
- lambda: 'return id(heater_counter) == 0;'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- lambda: |-
id(heater_start_temp) = id(temp_sensor).state;
- lambda: |-
id(heater_counter) = (id(heater_counter) + 1);
else:
- if:
condition:
and:
- lambda: 'return id(heat_value).state == 100;'
- lambda: 'return id(heater_counter) > 0;'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- lambda: |-
id(heater_counter) = (id(heater_counter) + 1);
else:
- lambda: |-
id(heater_counter) = 0;
- if:
condition:
and:
- lambda: 'return id(heater_counter) > 60;'
- lambda: 'return id(temp_sensor).state <= id(heater_start_temp);'
- lambda: 'return id(temp_sensor).state < 100;'
then:
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Heater On, Temperature Not Increasing"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
- if:
condition:
- lambda: 'return id(heater_counter) > 60;'
then:
- lambda: |-
id(heater_counter) = 0;
number:
- platform: template
name: Roast 1 Preheat Temp
optimistic: true
id: roast_1_preheat_temp
icon: "mdi:thermometer"
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 120
mode: box
- platform: template
name: Roast 1 Temp
optimistic: true
id: roast_1_temp
icon: "mdi:thermometer"
unit_of_measurement: °C
max_value: 270
min_value: 15
step: 1
initial_value: 221
mode: box
- platform: template
name: Roast 1 Ramp
optimistic: true
id: roast_1_ramp
icon: "mdi:thermometer-chevron-up"
unit_of_measurement: °C/min
max_value: 120
min_value: 1
step: 1
initial_value: 10
mode: box
- platform: template
name: Roast 1 Fan Speed
optimistic: true
id: roast_1_fan_speed
icon: "mdi:fan-speed-1"
unit_of_measurement: "%"
max_value: 100
min_value: 1
step: 1
initial_value: 75
mode: box
button:
- platform: template
name: "Start Roast #1"
id: start_roast_1
icon: "mdi:coffee"
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_1
globals:
- id: currently_roasting
type: int
restore_value: no
initial_value: '0'
- id: current_temp_setpoint
type: int
restore_value: no
initial_value: '0'
#Used for heater safety interval check
- id: heater_counter
type: int
restore_value: no
initial_value: '0'
- id: heater_start_temp
type: int
restore_value: no
initial_value: '0'
script:
- id: roast_timer
mode: restart
then:
- delay: 2700s
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Roast Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
- id: roast_1
then:
- globals.set:
id: currently_roasting
value: '1'
- light.turn_on:
id: button_light_switch_1
- light.turn_off:
id: button_light_switch_2
- light.turn_off:
id: button_light_switch_3
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 100%
blue: 0%
- fan.turn_on:
id: fan_speed
speed: !lambda |-
return id(roast_1_fan_speed).state;
- delay: 5s
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(roast_1_preheat_temp).state;
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Preheat"
- wait_until:
condition:
lambda: |-
return id(temp_sensor).state > (id(roast_1_preheat_temp).state - 1);
timeout: 300s
- if:
condition:
- lambda: |-
return id(temp_sensor).state < (id(roast_1_preheat_temp).state - 1);
then:
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: Preheat Timeout"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
else:
- delay: 120s
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 0%
blue: 0%
- globals.set:
id: current_temp_setpoint
value: !lambda |-
return id(roast_1_preheat_temp).state;
- script.execute: roast_timer
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Ramp-up"
- while:
condition:
or:
- lambda: |-
return id(temp_sensor).state < id(roast_1_temp).state;
#Was having issues with the roast ending early but not throwing errors. Changed from AND to OR above and added this to try at catch a situation where the temp sensor doesn't read a number (i.e. so it ISN'T less than the roast temp).
- lambda: |-
return isnan(id(temp_sensor).state);
then:
- climate.control:
id: pid_controller
mode: HEAT
target_temperature: !lambda |-
return id(current_temp_setpoint);
# delay will be calculated by dividing 60 by C/min value
- delay: !lambda |-
return 60000 / id(roast_1_ramp).state;
- if:
condition:
- lambda: |-
return id(current_temp_setpoint) < id(roast_1_temp).state;
then:
- lambda: |-
id(current_temp_setpoint) = (id(current_temp_setpoint) + 1);
else:
- script.stop: roast_timer
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 100%
green: 65%
blue: 0%
- climate.control:
id: pid_controller
mode: "OFF"
target_temperature: 15.5556°C
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Cooldown"
- wait_until:
condition:
sensor.in_range:
id: temp_sensor
below: 40
timeout: 1200s
- delay: 60s
- fan.turn_off: fan_speed
- globals.set:
id: currently_roasting
value: '0'
- text_sensor.template.publish:
id: roast_status
state: "Roast #1 Done"
- light.turn_on:
id: neopixel_light
brightness: 50%
red: 0%
green: 100%
blue: 0%
- id: blank_script
then:
light:
- platform: binary
name: $friendly_name Button Light 1
output: button_light_1
id: button_light_switch_1
restore_mode: ALWAYS_OFF
- platform: binary
name: $friendly_name Button Light 2
output: button_light_2
id: button_light_switch_2
restore_mode: ALWAYS_OFF
- platform: binary
name: $friendly_name Button Light 3
output: button_light_3
id: button_light_switch_3
restore_mode: ALWAYS_OFF
- platform: neopixelbus
type: RGB
variant: SK6812
pin: GPIO22
num_leds: 1
name: $friendly_name NeoPixel
id: neopixel_light
restore_mode: ALWAYS_OFF
default_transition_length: 0s
effects:
- pulse:
name: "Blink"
transition_length: 0s
update_interval: 1s
binary_sensor:
- platform: gpio
pin:
number: GPIO27
inverted: true
mode:
input: true
pullup: true
name: $friendly_name Button 1
on_press:
then:
- if:
condition:
lambda: 'return (id(currently_roasting) == 1);'
then:
else:
- script.execute: roast_1
- platform: gpio
pin:
number: GPIO32
inverted: true
mode:
input: true
pullup: true
name: $friendly_name Button 2
- platform: gpio
pin:
number: GPIO33
inverted: true
mode:
input: true
pullup: true
name: $friendly_name Button 3
output:
- id: button_light_1
platform: gpio
pin: GPIO4
- id: button_light_2
platform: gpio
pin: GPIO25
- id: button_light_3
platform: gpio
pin: GPIO26
- platform: ledc
pin: GPIO16
frequency: 1000 Hz #might need to change to get it to work?
id: fan_pwm
min_power: 0.2
zero_means_zero: true
- platform: slow_pwm
pin: GPIO17
id: heater_pwm
period: 15s
turn_on_action:
- fan.turn_on:
id: fan_speed
#Required this action and leaving it blank threw errors, so I just made a script to call that does nothing.
turn_off_action:
- script.execute: blank_script
fan:
- platform: speed
output: fan_pwm
name: $friendly_name Fan
id: fan_speed
restore_mode: ALWAYS_OFF
# - platform: speed
# output: heater_pwm
# name: $friendly_name Heater
# icon: mdi:heating-coil
# restore_mode: ALWAYS_OFF
climate:
- platform: pid
name: $friendly_name PID Controller
id: pid_controller
sensor: temp_sensor
default_target_temperature: 15.5556°C
heat_output: heater_pwm
control_parameters:
kp: 0.07276 #0.05009
ki: 0.00364 #0.00230
kd: 0.36376 #0.27302
visual:
min_temperature: 15.5556°C
max_temperature: 270°C
temperature_step: 1°C
switch:
- platform: template
name: $friendly_name PID Controller Autotune
turn_on_action:
- climate.pid.autotune: pid_controller
spi:
clk_pin: GPIO14 #14 #18
mosi_pin: GPIO13 #13 #23
miso_pin: GPIO12 #12 #19
sensor:
- platform: max31855
name: $friendly_name Temperature
cs_pin: GPIO15 #15 #5
update_interval: 1s
#filters:
# - lambda: return x * (9.0/5.0) + 32.0;
#unit_of_measurement: "°F"
id: temp_sensor
on_value_range:
- above: 280
then:
- script.stop: roast_1
- climate.control:
id: pid_controller
mode: "OFF"
- fan.turn_on:
id: fan_speed
speed: 100
- text_sensor.template.publish:
id: roast_status
state: "ERROR: OVERHEAT PROTECTION"
- light.turn_on:
id: neopixel_light
brightness: 100%
red: 100%
green: 0%
blue: 0%
effect: "Blink"
- platform: wifi_signal
name: $friendly_name WiFi Strength
update_interval: 60s
- platform: uptime
name: $friendly_name Uptime
- platform: pid
name: $friendly_name PID Kp
type: KP
- platform: pid
name: $friendly_name PID Ki
type: KI
- platform: pid
name: $friendly_name PID Kd
type: KD
- platform: pid
name: $friendly_name PID Heat
type: HEAT
id: heat_value
text_sensor:
- platform: wifi_info
ip_address:
name: $friendly_name IP Address
- platform: template
name: $friendly_name Status
id: roast_status
icon: "mdi:card-text"
status_led:
pin:
number: GPIO2
inverted: false
There’s a lot in there, but particularly relevant to you: I setup variables adjustable within HA that are then used in a long script I call whenever I start the roasting process.
As a follow up to this and if anyone stumbles on this thread in the future, the previous code that I posted is full of bugs and errors that I made, which cause compilation to fail. If there those errors are rectified, further bugs cause stack over flow errors and all sorts of other nasty things. I’ve edited the post containing that erroneous code to make that clear.;
But after a great deal of research and reading, I’ve ironed all that out and now have a working solution to repeat a actions every “n” minutes where “n” is variable. The variable(s) being global variables which can be altered via input numbers in HA. In summary, the actions are contained in a script and the script uses templated delays which use the global variables. To get the script to repeat constantly, I use “on_loop” which, according to the docs runs every 16ms or so. This on_loop checks if the script is running, and if it isn’t, it runs it (I discovered that repeatedly calling a script which is already running results in all sorts of nasty things happening).
Things to note (which I didn’t know until now) are that the delay only applies to the action(s) which immediately follow it. That is to say, it’s “non-blocking” as far as other things are concerned and any changes to sensor values etc will continue to happen and be published while the delay is running. Because of the way the “loop” runs, there will be a 16ms delay between each new execution of the script. In my case, the nearest minute or so will suffice so it isn’t an issue.
For the sake of completeness here is the fully working code that I used and which has been running flawlessly for 12+ hrs as I write this. I’ll mark this post as the solution to my OP. (The code also includes various adc sensors and an “enable” boolean which are not relevant to this topic).
esphome:
name: hydrotower1
friendly_name: HydroTower1
on_loop:
then:
- if:
condition:
not:
script.is_running: cycle_pump
then:
- script.execute: cycle_pump
esp32:
board: esp32dev
framework:
type: arduino
globals:
- id: pump_enabled
type: bool
restore_value: yes
initial_value: 'true'
- id: pump_interval
type: int
restore_value: yes
initial_value: '360'
- id: pump_on_time
type: int
restore_value: yes
initial_value: '60'
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "**************"
ota:
password: "****************"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Hydrotower1 Fallback Hotspot"
password: "*********"
captive_portal:
switch:
- platform: gpio
pin: GPIO14
inverted: true
name: "Pump Relay"
id: pump_relay
binary_sensor:
- platform: homeassistant
name: "Hydro Pump Enable"
entity_id: input_boolean.hydro_pump_enable
on_state:
then:
- lambda: |-
id(pump_enabled) = x;
sensor:
- platform: homeassistant
name: Hydro Pump Interval
entity_id: input_number.hydro_pump_interval
on_value:
then:
- lambda: |-
id(pump_interval) = x;
- platform: homeassistant
name: Hydro Pump On Time
entity_id: input_number.hydro_pump_on_time
on_value:
then:
- lambda: |-
id(pump_on_time) = x;
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Raw"
id: "tank_level_raw"
icon: mdi:car-coolant-level
update_interval: 60s
# change to true after testing
internal: False
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Percent"
id: "tank_level_percent"
icon: mdi:car-coolant-level
update_interval: 60s
internal: False
filters:
- calibrate_linear:
- 0.08 -> 0
- 1.52 -> 100
unit_of_measurement: "%"
- platform: adc
pin: GPIO34
attenuation: auto
name: "Tank Level Litres"
id: "tank_level_litres"
icon: mdi:car-coolant-level
update_interval: 60s
internal: False
# max is 57 litres at end of float ravel, 62 litres to brim so say 60 lites max
# offset is 15 litres before float moves
# so with these filters, 0 volts should show 15 litres, 0.95 volts should show 45+15 = 60 litres
filters:
- calibrate_linear:
- 0.08 -> 0
- 1.52 -> 45
- offset: 15
unit_of_measurement: "Litres"
script:
- id: cycle_pump
then:
- if:
condition:
lambda: |-
return id(pump_enabled) = true;
then:
- logger.log: "Started First (Interval-run time) Delay"
- delay: !lambda |-
return (id(pump_interval) - id(pump_on_time)) * 1000;
- switch.turn_on: pump_relay
- logger.log: "Started second (run time) Delay"
- delay: !lambda |-
return id(pump_on_time) * 1000;
- switch.turn_off: pump_relay
Thank you <3
This thread helped me a lot.
How did you get them inside HA to adjust the values?
I created number helpers. Those are the entities referred to above as platform homeassistant with the ids “input_number.hydro_pump_interval” and “input_number.hydro_pump_on_time”.