This post is for content relating to itās operation.
Iāll start with a high level description. It essentially works very similar to other irrigation and deep sleep projects projects around:
Wake up from deep sleep periodically and see if the plant needs water. If so, water, if not, back to sleep.
Donāt water the plant if battery is low, instead, go back to sleep for a long time and hope you get recharged on next wake.
Donāt water the plant if the tank level is low.
The watering regime might be described as āincremental drip-feed towards an upper target, followed by drying towards a lower thresholdā. This lets the plant dry out a bit, then āgives it a good water, but slowly rather than all at onceāā¦ I still need to monitor that this is working ok.
The light sensor is handy for checking how much light both the plant and the solar panel is getting.
Thereās some details over here for it actually. But itāll try to remember to tidy it up and add something here for it. It was harder to find simple info for it than expected. My final solution changed slightly to whatās in this link.
I moved to using 2 x 300kOhm resistors. Itās a simpler set-up. Thereās also a few comments in my config about the resistors used and scaling/conversions.
I made my first voltage divider. You need two resistors. I landed on 2 x 300kā¦
Wire them like below. I think itās right. (Edit, Its not quite right as battery power should go to esp battery terminal) wanted to keep the diagram simple and didnāt want to draw up my full wiring yet. You just add extra wires for power and ground as you need for your other devices. Edit: best to see the wiring diagram now.
Which led me to my 2 x 300kā¦ selection. I also like this āsymmetricā design. Doesnāt matter which way you wire it.
Battery sensor snippet
#Notes:
#Voltage divider: Used 2 x 300K Ohm resistors
- platform: adc
id: batt_voltage
name: Battery Voltage
internal: true
pin: ${batt_voltage_pin} #ADC1
update_interval: never
accuracy_decimals: 2
attenuation: auto
filters:
# #Scale it back up from voltage divided value 2 x 300K > 2.1. 4.2/2.1 = 2.
- multiply: 2
on_raw_value:
then:
#Sensor update counter.
- lambda: id(count_batt_voltage).publish_state(id(count_batt_voltage).state +1);
#Intermediate sensor. Might consolidate them later.
- platform: copy
source_id: batt_voltage
id: batt_voltage_filtered
icon: "mdi:battery"
internal: false
name: Battery Voltage
unit_of_measurement: V
accuracy_decimals: 2
filters:
- median: #Use moving median to smooth noise.
window_size: 10
send_every: 10
send_first_at: 10
#Convert the Voltage to a battery level (%)
- platform: copy
source_id: batt_voltage_filtered
id: batt_level
internal: false
icon: "mdi:battery"
name: Battery Percent
unit_of_measurement: '%'
accuracy_decimals: 0
filters:
# Map from voltage to Battery level
- calibrate_linear:
- 3.1 -> 0 #Set 3.0 to 0% even though it can go lower (2.4V), for life extention. There's not much capacity below this anyway.
- 4.1 -> 100 #Set 4.05 to 100% even though it can go higher (~4.2V), for life extention.
#Overide values less than 0% and more than 100%
- lambda: |
if (x < 0) return 0;
else if (x > 100) return 100;
else return ceil(x / 5) * 5;
on_value:
then:
#Publish that data is recieved
- binary_sensor.template.publish:
id: batt_level_recieved
state: ON
@Mahko_Mahko, Iām trying to learn from your code, but I canāt really make it work. Iām focusing on the deep sleep logic and Iām trying to understand how it works.
as you can see my esp is ignoring the button to prevent deep sleep and the fact to sleep during the night, it constantly follows the cycle defined by the variables, in my case 30min sleep, 30min run
run_duration: 30min
sleep_duration: 30min
can you help me to understand what Iām missing?
binary_sensor:
- platform: homeassistant
id: prevent_deep_sleep
entity_id: input_boolean.prevent_deep_sleep
icon: "mdi:sleep-off"
entity_category: diagnostic
on_press:
then:
- logger.log: "STAY AWAKE requested from HA. Preventing deep sleep"
- deep_sleep.prevent: deep_sleep_control
on_release:
then:
- logger.log: "STAY AWAKE TURNED OFF. Going to sleep..."
- deep_sleep.enter:
id: deep_sleep_control
sleep_duration: ${sleep_duration}
time:
- platform: homeassistant
id: homeassistant_time
on_time:
#Deep sleep at 8pm and wake up at 6am.
- hours: 20
then:
#Rest up
- deep_sleep.enter:
id: deep_sleep_control
until: "06:00:00"
time_id: homeassistant_time
deep_sleep:
id: deep_sleep_control
run_duration: ${run_duration}
sleep_duration: ${sleep_duration}
esphome:
on_boot:
- priority: -100
then:
- wait_until:
condition:
api.connected:
- logger.log: "API connected"
- wait_until:
condition:
time.has_time:
- logger.log: "Time has a value from Home Assistant"
- if:
condition:
binary_sensor.is_on: prevent_deep_sleep
then:
- logger.log: "Prevent deep sleep"
- deep_sleep.prevent: deep_sleep_control
else:
- logger.log: "Allow deep sleep"
- deep_sleep.allow: deep_sleep_control
on_shutdown:
priority: -100
then:
- if:
condition:
binary_sensor.is_on: prevent_deep_sleep
then:
- logger.log: "Prevent deep sleep"
- deep_sleep.prevent: deep_sleep_control
else:
- logger.log: "Allow deep sleep"
- deep_sleep.allow: deep_sleep_control
I had to work through some buggyness too at a few points.
Do you have any sensors with a high update_interval (say 1sec). Or are you managing the updates manually?
Try pulling them back to say 5sec for debugging.
Can you see the home assistant sensor being imported in the ESPHOME logs? Try making this NOT internal and check if it comes back re-imported into HA ok. So that it is clear everyone is hearing each other.
I also seem to recall sometimes the input Boolean only seemed to start working once you had toggled it from HA once the esp was online (once). So maybe fiddle with that.
In summary, Iām not sure. Mine is working great but I recall having to work through weird issues.
@Mar1us, almost
Iām now able to prevent deep sleep with the button, but when It enters in deep sleep on sunset it never resume still study/test/fail/learnā¦
# Time configuration
time:
- platform: homeassistant
id: homeassistant_time
# Sun configuration
sun:
latitude: 9999999999999 #replace with yours
longitude: 9999999999999 #replace with yours
id: sun_component
on_sunset:
then:
- logger.log: "Sunset occurred, entering deep sleep mode until next sunrise"
- deep_sleep.enter:
id: deep_sleep_control
sleep_duration: !lambda "return id(sleep_duration_calc).state * 60;"
# Deep sleep configuration
deep_sleep:
id: deep_sleep_control
#run_duration: 5min # Run for 5 minutes before entering deep sleep mode
#sleep_duration: 5min # Sleep for 5 minutes before waking up again
binary_sensor:
# Get the state of the prevent_deep_sleep input_boolean from Home Assistant to control whether or not to enter deep sleep mode
- platform: homeassistant
id: prevent_deep_sleep
entity_id: input_boolean.prevent_deep_sleep
icon: "mdi:sleep-off"
entity_category: diagnostic
sensor:
# Get the sleep_duration value from Home Assistant to control how long to sleep for in deep sleep mode
- platform: homeassistant
id: sleep_duration
entity_id: input_number.deep_sleep_sleep_duration
# Get the delay_duration value from Home Assistant to control how long (in seconds) to wait before proceeding
- platform: homeassistant
id: delay_duration
entity_id: input_number.deep_sleep_script_delay_duration
##################
- platform: template
name: "Sleep Duration to next Sunrise"
id: sleep_duration_calc
unit_of_measurement: "min"
lambda: |-
if (id(tomorrow_sunrise_string).has_state() && id(today_sunset_string).has_state()) {
struct tm tomorrow_sunrise_tm;
strptime(id(tomorrow_sunrise_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &tomorrow_sunrise_tm);
time_t tomorrow_sunrise_time = mktime(&tomorrow_sunrise_tm);
struct tm today_sunset_tm;
strptime(id(today_sunset_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &today_sunset_tm);
time_t today_sunset_time = mktime(&today_sunset_tm);
double sleep_duration_seconds = difftime(tomorrow_sunrise_time, today_sunset_time);
return sleep_duration_seconds / 60.0;
}
return {};
##################
text_sensor:
- platform: homeassistant
id: today_sunrise_string
entity_id: sensor.suninfo_sunrise
attribute: today
internal: true
- platform: homeassistant
id: today_sunset_string
entity_id: sensor.suninfo_sunset
attribute: today
internal: true
- platform: homeassistant
id: tomorrow_sunrise_string
entity_id: sensor.suninfo_sunrise
attribute: tomorrow
internal: true
- platform: homeassistant
id: tomorrow_sunset_string
entity_id: sensor.suninfo_sunset
attribute: tomorrow
internal: true
- platform: template
name: "Today Sunset"
id: today_sunset
lambda: |-
if (id(today_sunset_string).has_state()) {
struct tm t;
strptime(id(today_sunset_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &t);
char buf[30];
strftime(buf, sizeof(buf), "%B %d, %Y at %I:%M %p", &t);
return std::string(buf);
}
return {};
- platform: template
name: "Tomorrow Sunrise"
id: tomorrow_sunrise
lambda: |-
if (id(tomorrow_sunrise_string).has_state()) {
struct tm t;
strptime(id(tomorrow_sunrise_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &t);
char buf[9];
strftime(buf, sizeof(buf), "%H:%M:%S", &t);
return std::string(buf);
}
return {};
esphome:
on_boot:
priority: -10
then:
- logger.log:
format: "Waiting for API to connect..."
# Wait until API is connected and time is synchronized before proceeding
- wait_until:
api.connected:
- logger.log:
format: "API connected. Waiting for time to synchronize..."
- wait_until:
time.has_time:
- logger.log:
format: "Time synchronized. Checking prevent_deep_sleep binary sensor value..."
# Check if the prevent_deep_sleep binary sensor value is on or off before proceeding
- if:
condition:
lambda: 'return id(prevent_deep_sleep).state;'
then:
- logger.log:
format: "prevent_deep_sleep binary sensor value is on"
else:
- logger.log:
format: "prevent_deep_sleep binary sensor value is off"
# Log that API is connected, time is synchronized, and prevent_deep_sleep binary sensor value is checked
- logger.log:
format: "API connected, time synchronized, and prevent_deep_sleep binary sensor value checked."
- wait_until:
lambda: "return id(today_sunset).has_state() && id(tomorrow_sunrise).has_state();"
- wait_until:
lambda: "return id(prevent_deep_sleep).has_state() && id(sleep_duration).has_state() && id(delay_duration).has_state();"
- script.execute: consider_deep_sleep
script:
- id: consider_deep_sleep
mode: queued
then:
- logger.log:
format: "Executing consider_deep_sleep script"
- delay: 10s
- if:
condition:
binary_sensor.is_on: prevent_deep_sleep
then:
- logger.log:
format: "Skipping sleep, per prevent_deep_sleep"
- deep_sleep.prevent: deep_sleep_control
else:
- logger.log:
format: "Entering deep sleep mode"
- if:
condition:
- sun.is_above_horizon:
id: sun_component # Sun is above horizon.
then:
- logger.log:
format: "Entering deep sleep until sunrise"
- deep_sleep.enter:
id: deep_sleep_control
#sleep_duration: 2min
sleep_duration: !lambda "return id(sleep_duration_calc).state * 60;"
else:
- logger.log:
format: "Entering deep sleep normal"
- deep_sleep.enter:
id: deep_sleep_control
#sleep_duration: 2min
sleep_duration: !lambda "return id(delay_duration).state;"
- script.execute: consider_deep_sleep
@Mar1us thank you, because after your message I provided more love to the code; it seems that now it works
The button to suspend deep sleep works
The input to control the deep sleep duration and the delay duration are working
The sunset condition seems ok (testing now)
essentially this is the behavior:
if the prevent deep sleep is on, the esp remains up
if the prevent deep sleep is off, it goes in deep sleep for the amount of minutes defined by the input sleep duration, then the script is delayed for the amount of time defined by the input number script delat duration. This is the normal condition, when the sun is below the horizontal then the esp goes in deep sleep until the next sunrise. (in order to calculate the sunst/sunrise duration I used the SUN2 component)
here the full code, testing it right now.
any feedback/comments is really apprecated
# Substitutions
# Define variables to be used throughout the configuration
substitutions:
device_name: test
friendly_name: "test"
device_platform: espressif32
device_board: nodemcu-32s
device_ip: X.X.X.X
# Packages
# Include common configurations from external files
packages:
wifi: !include common/device_wifi.yaml
device_base: !include common/device_base_ESP32.yaml
home_assistant_api: !include common/device_api.yaml
sensor_wifi_ip_address: !include common/sensor_wifi_ip_address.yaml
# Enable logging
logger:
level: VERBOSE
# API
api:
# Time configuration
time:
- platform: homeassistant
id: homeassistant_time
# Sun configuration
sun:
latitude: 999999
longitude: 99999
id: sun_component
on_sunset:
then:
- logger.log: "Sunset occurred, entering deep sleep mode until next sunrise"
- logger.log:
format: "Sleep duration until next sunrise: %f minutes"
args: [ "id(sleep_duration_calc).state" ]
- deep_sleep.enter:
id: deep_sleep_control
sleep_duration: !lambda "return id(sleep_duration_calc).state * 60000;"
# Deep sleep configuration
deep_sleep:
id: deep_sleep_control
binary_sensor:
# Get the state of the prevent_deep_sleep input_boolean from Home Assistant to control whether or not to enter deep sleep mode
- platform: homeassistant
id: prevent_deep_sleep
entity_id: input_boolean.prevent_deep_sleep
icon: "mdi:sleep-off"
entity_category: diagnostic
sensor:
# Get the sleep_duration value from Home Assistant to control how long to sleep for in deep sleep mode
- platform: homeassistant
id: sleep_duration
entity_id: input_number.deep_sleep_sleep_duration
# Get the delay_duration value from Home Assistant to control how long (in seconds) to wait before proceeding
- platform: homeassistant
id: delay_duration
entity_id: input_number.deep_sleep_script_delay_duration
##################
- platform: template
name: "Sleep Duration to next Sunrise"
id: sleep_duration_calc
unit_of_measurement: "min"
lambda: |-
if (id(tomorrow_sunrise_string).has_state() && id(today_sunset_string).has_state()) {
struct tm tomorrow_sunrise_tm;
strptime(id(tomorrow_sunrise_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &tomorrow_sunrise_tm);
time_t tomorrow_sunrise_time = mktime(&tomorrow_sunrise_tm);
struct tm today_sunset_tm;
strptime(id(today_sunset_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &today_sunset_tm);
time_t today_sunset_time = mktime(&today_sunset_tm);
double sleep_duration_seconds = difftime(tomorrow_sunrise_time, today_sunset_time);
return sleep_duration_seconds / 60.0;
}
return {};
##################
text_sensor:
- platform: homeassistant
id: today_sunrise_string
entity_id: sensor.suninfo_sunrise
attribute: today
internal: true
- platform: homeassistant
id: today_sunset_string
entity_id: sensor.suninfo_sunset
attribute: today
internal: true
- platform: homeassistant
id: tomorrow_sunrise_string
entity_id: sensor.suninfo_sunrise
attribute: tomorrow
internal: true
- platform: homeassistant
id: tomorrow_sunset_string
entity_id: sensor.suninfo_sunset
attribute: tomorrow
internal: true
- platform: template
name: "Today Sunset"
id: today_sunset
lambda: |-
if (id(today_sunset_string).has_state()) {
struct tm t;
strptime(id(today_sunset_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &t);
char buf[30];
strftime(buf, sizeof(buf), "%B %d, %Y at %I:%M %p", &t);
return std::string(buf);
}
return {};
- platform: template
name: "Tomorrow Sunrise"
id: tomorrow_sunrise
lambda: |-
if (id(tomorrow_sunrise_string).has_state()) {
struct tm t;
strptime(id(tomorrow_sunrise_string).state.c_str(), "%Y-%m-%d %H:%M:%S", &t);
char buf[9];
strftime(buf, sizeof(buf), "%H:%M:%S", &t);
return std::string(buf);
}
return {};
esphome:
on_boot:
priority: -10
then:
- logger.log:
format: "Waiting for API to connect..."
# Wait until API is connected and time is synchronized before proceeding
- wait_until:
api.connected:
- logger.log:
format: "API connected. Waiting for time to synchronize..."
- wait_until:
time.has_time:
- logger.log:
format: "Time synchronized. Checking prevent_deep_sleep binary sensor value..."
# Check if the prevent_deep_sleep binary sensor value is on or off before proceeding
- if:
condition:
lambda: 'return id(prevent_deep_sleep).state;'
then:
- logger.log:
format: "prevent_deep_sleep binary sensor value is on"
else:
- logger.log:
format: "prevent_deep_sleep binary sensor value is off"
# Log that API is connected, time is synchronized, and prevent_deep_sleep binary sensor value is checked
- logger.log:
format: "API connected, time synchronized, and prevent_deep_sleep binary sensor value checked."
- wait_until:
lambda: "return id(today_sunset).has_state() && id(tomorrow_sunrise).has_state();"
- wait_until:
lambda: "return id(prevent_deep_sleep).has_state() && id(sleep_duration).has_state() && id(delay_duration).has_state();"
- script.execute: consider_deep_sleep
script:
- id: consider_deep_sleep
mode: queued
then:
- logger.log:
format: "Executing consider_deep_sleep script"
- logger.log:
format: "Delaying for %d minutes"
args: [ "(int) id(delay_duration).state" ]
- delay: !lambda "return id(delay_duration).state * 60000;"
- logger.log:
format: "After the delay"
- if:
condition:
binary_sensor.is_on: prevent_deep_sleep
then:
- logger.log:
format: "Skipping sleep, per prevent_deep_sleep"
else:
- logger.log:
format: "Entering deep sleep mode"
- if:
condition:
- sun.is_above_horizon:
id: sun_component # Sun is above horizon.
then:
- logger.log:
format: "Entering deep sleep normal"
- logger.log:
format: "Sleep for %d minutes"
args: [ "(int) id(sleep_duration).state" ]
- deep_sleep.enter:
id: deep_sleep_control
sleep_duration: !lambda "return id(sleep_duration).state * 60000;"
else:
- logger.log:
format: "Entering deep sleep until sunrise"
- logger.log:
format: "Sleep duration until next sunrise: %f minutes"
args: [ "id(sleep_duration_calc).state" ]
- deep_sleep.enter:
id: deep_sleep_control
sleep_duration: !lambda "return id(sleep_duration_calc).state * 60000;"
- script.execute: consider_deep_sleep
Hi Folks, I would like to have your opinion here
Deep sleep cycle it fine to reduce the power consumption, but the ESP must remain on during the watering cycle.
how you calculated the battery capacity/pump consumption/solar panel?
Iām looking around to select the hw
Materials List
1st pass at materials list. May not be complete yet. I included a few extra links to things like soldering equipment and crimping kit I have.
Get a good ESP32 for this project. Get one optimised for low energy / deep sleep with battery connections. Tried other boards but idel current too high.