Last active
May 1, 2025 23:17
-
-
Save pavax/7db3cd531fd94854c609c8274d4b8136 to your computer and use it in GitHub Desktop.
ESPHome Config for Plant Watering
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
substitutions: | |
friendly_name: "Smart Plant Watering 2" | |
name: "smart-plant-watering-2" | |
initial_sleep_duration_minutes: "120" # For how long (minutes) should the MCU sleep | |
initial_watering_wait_time_minutes: "240" # Time (minutes) to wait before watering the plant again | |
initial_min_moisture_level: "15" # Threshold that defines when to water a plant | |
initial_max_watering_time_seconds: "10" # How long should the watering process run (seconds) | |
initial_max_running_time_minutes: "5" # Max time (minutes) for the MCU to stay awake | |
battery_max: "4.1" # Battery voltage indicating 100% | |
battery_min: "3.3" # Battery voltage indicating 0% | |
uptime_update_interval: 60s | |
esp32: | |
board: lolin_c3_mini | |
framework: | |
type: arduino | |
packages: | |
device_base: !include common/device_base.yaml | |
wifi: | |
ssid: !secret wifi_ssid | |
password: !secret wifi_password | |
fast_connect: true | |
output_power: 8.5dB | |
esphome: | |
on_boot: | |
- priority: 600 | |
then: | |
- delay: 1s | |
- script.execute: led_status_light | |
- priority: -100 | |
then: | |
- script.execute: measure_battery | |
- script.wait: measure_battery | |
- lambda: "id(has_booted) = true;" | |
on_shutdown: | |
- priority: -100.0 | |
then: | |
- switch.turn_off: external_modules | |
- component.update: uptime_seconds | |
- component.update: uptime_text_sensor | |
- logger.log: | |
level: INFO | |
format: "Going to sleep - Good Night!🌛" | |
safe_mode: | |
disabled: true | |
logger: | |
level: DEBUG | |
globals: | |
- id: plant1_status | |
type: std::string | |
restore_value: no | |
initial_value: '"unknown"' | |
- id: plant2_status | |
type: std::string | |
restore_value: no | |
initial_value: '"unknown"' | |
- id: plant3_status | |
type: std::string | |
restore_value: no | |
initial_value: '"unknown"' | |
- id: plant1_last_watered | |
type: unsigned long | |
restore_value: yes | |
initial_value: "0" | |
- id: plant2_last_watered | |
type: unsigned long | |
restore_value: yes | |
initial_value: "0" | |
- id: plant3_last_watered | |
type: unsigned long | |
restore_value: yes | |
initial_value: "0" | |
- id: has_booted | |
type: bool | |
restore_value: no | |
initial_value: "false" | |
deep_sleep: | |
id: sleep_control | |
esp32_ble_tracker: | |
scan_parameters: | |
# When using this component on single core chips such as the ESP32-C3 both WiFi and ble_tracker must run on the same core, | |
# and this has been known to cause issues when connecting to WiFi. A work-around for this is to enable the tracker only after | |
# the native API is connected. | |
continuous: false | |
active: false | |
interval: 300ms | |
window: 300ms | |
#web_server: | |
# port: 80 | |
interval: | |
- interval: 5s | |
startup_delay: 60s | |
then: | |
- if: | |
condition: | |
- and: | |
- lambda: "return id(uptime_seconds).state >= (id(max_running_time_minutes).state * 60);" | |
- switch.is_off: relay1 | |
- switch.is_off: relay2 | |
- switch.is_off: relay3 | |
then: | |
- logger.log: | |
level: WARN | |
format: "Max running time reached: %0.0fs of max. %0.0fs" | |
args: | |
- id(uptime_seconds).state | |
- id(max_running_time_minutes).state * 60 | |
- light.turn_off: | |
id: status_led | |
transition_length: 0s | |
- delay: 2s | |
- deep_sleep.allow: sleep_control | |
- deep_sleep.enter: | |
id: sleep_control | |
sleep_duration: !lambda "return id(sleep_duration_minutes).state * 1000 * 60;" | |
- interval: 5sec | |
startup_delay: 20s | |
then: | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant1_status) == std::string("ok") || id(plant1_status) == std::string("watered") || id(plant1_status) == std::string("already_watered");' | |
- lambda: 'return id(plant2_status) == std::string("ok") || id(plant2_status) == std::string("watered") || id(plant2_status) == std::string("already_watered");' | |
- lambda: 'return id(plant3_status) == std::string("ok") || id(plant3_status) == std::string("watered") || id(plant3_status) == std::string("already_watered");' | |
then: | |
- esp32_ble_tracker.stop_scan | |
- if: | |
condition: | |
switch.is_off: prevent_deep_sleep_switch | |
then: | |
- logger.log: | |
level: INFO | |
format: "All plants are either ok or have been watered." | |
- light.turn_off: | |
id: status_led | |
transition_length: 0s | |
- delay: 2s | |
- deep_sleep.enter: | |
id: sleep_control | |
sleep_duration: !lambda "return id(sleep_duration_minutes).state * 1000 * 60;" | |
light: | |
- platform: neopixelbus | |
id: status_led | |
type: GRB | |
pin: GPIO7 | |
num_leds: 1 | |
name: "Onboard RGB" | |
variant: ws2812 | |
effects: | |
- pulse: | |
name: "Pulse" | |
transition_length: 700ms | |
update_interval: 700ms | |
output: | |
- platform: gpio | |
id: battery_gnd | |
pin: | |
number: GPIO2 | |
inverted: true | |
mode: | |
output: true | |
pullup: false | |
pulldown: false | |
binary_sensor: | |
- platform: gpio | |
id: water_tank_empty | |
name: "Water Tank" | |
# ON means problem detected (tank is empty) whereas OFF means no problem (tank is full). | |
device_class: problem | |
pin: | |
number: GPIO1 | |
inverted: false | |
mode: | |
input: true | |
pullup: true | |
filters: | |
- delayed_on: 250ms | |
- delayed_off: 250ms | |
on_release: | |
then: | |
- script.execute: led_status_light | |
- logger.log: | |
level: DEBUG | |
format: "Wasserstand OK" | |
on_press: | |
then: | |
- script.execute: led_status_light | |
- logger.log: | |
level: DEBUG | |
format: "Wasserstand niedrig" | |
- lambda: |- | |
if (id(relay1).state) { | |
id(relay1).turn_off(); | |
} | |
if (id(relay2).state) { | |
id(relay2).turn_off(); | |
} | |
if (id(relay3).state) { | |
id(relay3).turn_off(); | |
} | |
sensor: | |
- platform: adc | |
pin: GPIO3 | |
name: "Battery Voltage" | |
id: battery_voltage | |
icon: "mdi:flash" | |
attenuation: 11db | |
accuracy_decimals: 2 | |
update_interval: never | |
filters: | |
- multiply: 2.0 | |
- round: 2 | |
- platform: copy | |
source_id: battery_voltage | |
unit_of_measurement: "%" | |
icon: "mdi:battery" | |
name: "Battery Percentage" | |
accuracy_decimals: 0 | |
filters: | |
- lambda: |- | |
const float max_voltage = ${battery_max}; | |
const float min_voltage = ${battery_min}; | |
float battery_percentage = (x - min_voltage) / (max_voltage - min_voltage) * 100.0; | |
return battery_percentage > 100.0 ? 100.0 : (battery_percentage < 0.0 ? 0.0 : battery_percentage); | |
- round: 0 | |
- platform: uptime | |
id: uptime_seconds | |
type: seconds | |
name: Uptime Seconds | |
update_interval: ${uptime_update_interval} | |
- platform: xiaomi_hhccjcy01 | |
mac_address: "C4:7C:8D:65:FD:DF" | |
moisture: | |
name: "Plant 1 Soil Moisture" | |
on_value: | |
then: | |
- logger.log: | |
level: DEBUG | |
format: "Plant 1: Moisture received: %.1f" | |
args: [x] | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant1_status) == std::string("unknown");' | |
- lambda: "return x < id(plant_1_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: WARN | |
format: "Plant 1: Soil dry - Schedule watering!" | |
- globals.set: | |
id: plant1_status | |
value: '"needs_water"' | |
- script.execute: | |
id: schedule_watering | |
relay_id: !lambda "return id(plant1_relay).state;" | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant1_status) == std::string("unknown");' | |
- lambda: "return x > id(plant_1_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: INFO | |
format: "Plant 1: Soil moisture OK — no watering needed." | |
- globals.set: | |
id: plant1_status | |
value: '"ok"' | |
- component.update: plant1_status_sensor | |
- platform: xiaomi_hhccjcy01 | |
mac_address: "C4:7C:8D:6B:A8:3C" | |
moisture: | |
name: "Plant 2 Soil Moisture" | |
on_value: | |
then: | |
- logger.log: | |
level: INFO | |
format: "Plant 2: Moisture received: %.1f" | |
args: [x] | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant2_status) == std::string("unknown");' | |
- lambda: "return x < id(plant_2_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: WARN | |
format: "Plant 2: Soil dry - Schedule watering!" | |
- globals.set: | |
id: plant2_status | |
value: '"needs_water"' | |
- script.execute: | |
id: schedule_watering | |
relay_id: !lambda "return id(plant2_relay).state;" | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant2_status) == std::string("unknown");' | |
- lambda: "return x > id(plant_2_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: INFO | |
format: "Plant 2: Soil moisture OK — no watering needed." | |
- globals.set: | |
id: plant2_status | |
value: '"ok"' | |
- component.update: plant2_status_sensor | |
- platform: xiaomi_hhccjcy01 | |
mac_address: "C4:7C:8D:6B:8E:EE" | |
moisture: | |
name: "Plant 3 Soil Moisture" | |
on_value: | |
then: | |
- logger.log: | |
level: INFO | |
format: "Plant 3: Moisture received: %.1f" | |
args: [x] | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant3_status) == std::string("unknown");' | |
- lambda: "return x < id(plant_3_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: WARN | |
format: "Plant 3: Soil dry - Schedule watering!" | |
- globals.set: | |
id: plant3_status | |
value: '"needs_water"' | |
- script.execute: | |
id: schedule_watering | |
relay_id: !lambda "return id(plant3_relay).state;" | |
- if: | |
condition: | |
and: | |
- lambda: 'return id(plant3_status) == std::string("unknown");' | |
- lambda: "return x > id(plant_3_min_moisture_level).state;" | |
then: | |
- logger.log: | |
level: INFO | |
format: "Plant 3: Soil moisture OK — no watering needed." | |
- globals.set: | |
id: plant3_status | |
value: '"ok"' | |
- component.update: plant3_status_sensor | |
- platform: template | |
name: "Wakeup Cause" | |
accuracy_decimals: 0 | |
entity_category: diagnostic | |
lambda: return esp_sleep_get_wakeup_cause(); | |
- platform: template | |
name: "Plant 1 Last Watered Timestamp" | |
id: plant1_last_watered_timestamp_sensor | |
unit_of_measurement: "s" | |
accuracy_decimals: 0 | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
return id(plant1_last_watered); | |
- platform: template | |
name: "Plant 2 Last Watered Timestamp" | |
id: plant2_last_watered_timestamp_sensor | |
unit_of_measurement: "s" | |
accuracy_decimals: 0 | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
return id(plant2_last_watered); | |
- platform: template | |
name: "Plant 3 Last Watered Timestamp" | |
id: plant3_last_watered_timestamp_sensor | |
unit_of_measurement: "s" | |
accuracy_decimals: 0 | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
return id(plant3_last_watered); | |
text_sensor: | |
- id: !extend uptime_text_sensor | |
update_interval: ${uptime_update_interval} | |
- platform: template | |
id: plant1_status_sensor | |
name: "Plant-1 Status" | |
update_interval: never | |
lambda: return id(plant1_status); | |
- platform: template | |
id: plant2_status_sensor | |
name: "Plant-2 Status" | |
update_interval: never | |
lambda: return id(plant2_status); | |
- platform: template | |
id: plant3_status_sensor | |
name: "Plant-3 Status" | |
update_interval: never | |
lambda: return id(plant3_status); | |
- platform: template | |
name: "Plant 1 Last Watered" | |
id: plant1_last_watered_text_sensor | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
if (id(plant1_last_watered) == 0) { | |
return std::string("Never"); | |
} else { | |
time_t last_time = id(plant1_last_watered); | |
struct tm *timeinfo = localtime(&last_time); | |
char buffer[30]; | |
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); | |
return std::string(buffer); | |
} | |
- platform: template | |
name: "Plant 2 Last Watered" | |
id: plant2_last_watered_text_sensor | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
if (id(plant2_last_watered) == 0) { | |
return std::string("Never"); | |
} else { | |
time_t last_time = id(plant2_last_watered); | |
struct tm *timeinfo = localtime(&last_time); | |
char buffer[30]; | |
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); | |
return std::string(buffer); | |
} | |
- platform: template | |
name: "Plant 3 Last Watered" | |
id: plant3_last_watered_text_sensor | |
entity_category: diagnostic | |
update_interval: never | |
lambda: |- | |
if (id(plant3_last_watered) == 0) { | |
return std::string("Never"); | |
} else { | |
time_t last_time = id(plant3_last_watered); | |
struct tm *timeinfo = localtime(&last_time); | |
char buffer[30]; | |
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", timeinfo); | |
return std::string(buffer); | |
} | |
switch: | |
- platform: template | |
name: "Relay 1" | |
id: relay1 | |
lambda: "return id(relay1_internal).state;" | |
turn_on_action: | |
- if: | |
condition: | |
and: | |
- binary_sensor.is_off: water_tank_empty | |
- lambda: "return id(has_booted);" | |
then: | |
- switch.turn_on: external_modules | |
- switch.turn_on: relay1_internal | |
- script.execute: | |
id: update_plants_state | |
relay_id: "relay1" | |
newState: "watering" | |
- script.execute: | |
id: relay_timer_script | |
relay_id: "relay1" | |
else: | |
- logger.log: | |
level: WARN | |
format: "Blocking relay1 activation!" | |
turn_off_action: | |
- switch.turn_off: relay1_internal | |
- script.stop: relay_timer_script | |
- if: | |
condition: | |
- lambda: "return id(has_booted);" | |
then: | |
- script.execute: | |
id: mark_plants_as_watered | |
relay_id: "relay1" | |
- platform: template | |
name: "Relay 2" | |
id: relay2 | |
lambda: "return id(relay2_internal).state;" | |
turn_on_action: | |
- if: | |
condition: | |
and: | |
- binary_sensor.is_off: water_tank_empty | |
- lambda: "return id(has_booted);" | |
then: | |
- switch.turn_on: external_modules | |
- switch.turn_on: relay2_internal | |
- script.execute: | |
id: update_plants_state | |
relay_id: "relay2" | |
newState: "watering" | |
- script.execute: | |
id: relay_timer_script | |
relay_id: "relay2" | |
else: | |
- logger.log: | |
level: WARN | |
format: "Blocking relay2 activation!" | |
turn_off_action: | |
- switch.turn_off: relay2_internal | |
- script.stop: relay_timer_script | |
- if: | |
condition: | |
- lambda: "return id(has_booted);" | |
then: | |
- script.execute: | |
id: mark_plants_as_watered | |
relay_id: "relay2" | |
- platform: template | |
name: "Relay 3" | |
id: relay3 | |
lambda: "return id(relay3_internal).state;" | |
turn_on_action: | |
- if: | |
condition: | |
and: | |
- binary_sensor.is_off: water_tank_empty | |
- lambda: "return id(has_booted);" | |
then: | |
- switch.turn_on: external_modules | |
- switch.turn_on: relay3_internal | |
- script.execute: | |
id: update_plants_state | |
relay_id: "relay3" | |
newState: "watering" | |
- script.execute: | |
id: relay_timer_script | |
relay_id: "relay3" | |
else: | |
- logger.log: | |
level: WARN | |
format: "Blocking relay3 activation!" | |
turn_off_action: | |
- switch.turn_off: relay3_internal | |
- script.stop: relay_timer_script | |
- if: | |
condition: | |
- lambda: "return id(has_booted);" | |
then: | |
- script.execute: | |
id: mark_plants_as_watered | |
relay_id: "relay3" | |
- platform: gpio | |
name: "Relay 1 (Internal)" | |
id: relay1_internal | |
restore_mode: ALWAYS_OFF | |
pin: GPIO4 | |
inverted: true | |
interlock: [relay2_internal, relay3_internal] | |
internal: true | |
- platform: gpio | |
name: "Relay 2 (internal)" | |
id: relay2_internal | |
restore_mode: ALWAYS_OFF | |
pin: GPIO8 | |
inverted: true | |
interlock: [relay1_internal, relay3_internal] | |
internal: true | |
- platform: gpio | |
name: "Relay 3 (internal)" | |
id: relay3_internal | |
restore_mode: ALWAYS_OFF | |
pin: GPIO6 | |
inverted: true | |
interlock: [relay1_internal, relay2_internal] | |
internal: true | |
- platform: gpio | |
name: "Power external modules" | |
id: external_modules | |
restore_mode: ALWAYS_OFF | |
pin: | |
number: GPIO5 | |
inverted: false | |
on_turn_off: | |
- lambda: |- | |
if (id(relay1).state) { | |
id(relay1).turn_off(); | |
} | |
if (id(relay2).state) { | |
id(relay2).turn_off(); | |
} | |
if (id(relay3).state) { | |
id(relay3).turn_off(); | |
} | |
- platform: template | |
name: "Prevent Deep Sleep" | |
id: prevent_deep_sleep_switch | |
restore_mode: ALWAYS_OFF | |
optimistic: true | |
entity_category: config | |
turn_on_action: | |
- deep_sleep.prevent: sleep_control | |
turn_off_action: | |
- deep_sleep.allow: sleep_control | |
number: | |
- platform: template | |
name: "Sleep Duration" | |
id: sleep_duration_minutes | |
unit_of_measurement: "min" | |
entity_category: config | |
min_value: 1 | |
max_value: 1440 | |
step: 5 | |
optimistic: true | |
mode: box | |
initial_value: ${initial_sleep_duration_minutes} | |
restore_value: yes | |
- platform: template | |
name: "Max Running Time" | |
id: max_running_time_minutes | |
unit_of_measurement: "min" | |
entity_category: config | |
min_value: 1 | |
max_value: 15 | |
step: 1 | |
optimistic: true | |
mode: box | |
initial_value: ${initial_max_running_time_minutes} | |
restore_value: yes | |
- platform: template | |
name: "Max Watering Time" | |
id: max_watering_time | |
unit_of_measurement: "sec" | |
entity_category: config | |
min_value: 0 | |
max_value: 90 | |
step: 1 | |
optimistic: true | |
mode: box | |
initial_value: ${initial_max_watering_time_seconds} | |
restore_value: yes | |
- platform: template | |
name: "Watering wait time duration" | |
id: watering_wait_time_minutes | |
entity_category: config | |
unit_of_measurement: "min" | |
min_value: 0 | |
max_value: 10080 | |
step: 10 | |
optimistic: true | |
mode: box | |
initial_value: ${initial_watering_wait_time_minutes} | |
restore_value: yes | |
- platform: template | |
name: "Plant 1 min. Moisture Level" | |
id: plant_1_min_moisture_level | |
entity_category: config | |
unit_of_measurement: "%" | |
min_value: 0 | |
max_value: 100 | |
step: 1 | |
optimistic: true | |
initial_value: ${initial_min_moisture_level} | |
restore_value: yes | |
- platform: template | |
name: "Plant 2 min. Moisture Level" | |
id: plant_2_min_moisture_level | |
entity_category: config | |
unit_of_measurement: "%" | |
min_value: 0 | |
max_value: 100 | |
step: 1 | |
optimistic: true | |
initial_value: ${initial_min_moisture_level} | |
restore_value: yes | |
- platform: template | |
name: "Plant 3 min. Moisture Level" | |
id: plant_3_min_moisture_level | |
entity_category: config | |
unit_of_measurement: "%" | |
min_value: 0 | |
max_value: 100 | |
step: 1 | |
optimistic: true | |
initial_value: ${initial_min_moisture_level} | |
restore_value: yes | |
select: | |
- platform: template | |
name: "Plant-1" | |
id: plant1_relay | |
options: | |
- "relay1" | |
- "relay2" | |
- "relay3" | |
entity_category: config | |
initial_option: "relay1" | |
restore_value: yes | |
optimistic: true | |
- platform: template | |
name: "Plant-2" | |
id: plant2_relay | |
options: | |
- "relay1" | |
- "relay2" | |
- "relay3" | |
entity_category: config | |
initial_option: "relay2" | |
restore_value: yes | |
optimistic: true | |
- platform: template | |
name: "Plant-3" | |
id: plant3_relay | |
options: | |
- "relay1" | |
- "relay2" | |
- "relay3" | |
entity_category: config | |
initial_option: "relay3" | |
restore_value: yes | |
optimistic: true | |
api: | |
on_client_connected: | |
- script.execute: led_status_light | |
- component.update: plant1_last_watered_timestamp_sensor | |
- component.update: plant2_last_watered_timestamp_sensor | |
- component.update: plant3_last_watered_timestamp_sensor | |
- component.update: plant1_last_watered_text_sensor | |
- component.update: plant2_last_watered_text_sensor | |
- component.update: plant3_last_watered_text_sensor | |
- esp32_ble_tracker.start_scan: | |
continuous: true | |
on_client_disconnected: | |
- script.execute: led_status_light | |
- esp32_ble_tracker.stop_scan: | |
services: | |
- service: watering_service | |
variables: | |
relay_id: string | |
then: | |
- script.execute: | |
id: schedule_watering | |
relay_id: !lambda |- | |
return relay_id; | |
script: | |
- id: measure_battery | |
mode: single | |
then: | |
- output.turn_on: battery_gnd | |
- delay: 1s | |
- component.update: battery_voltage | |
- delay: 1s | |
- output.turn_off: battery_gnd | |
- id: led_status_light | |
mode: restart | |
then: | |
- lambda: |- | |
// Tank Empty -> Red | |
if (id(water_tank_empty).state) { | |
id(status_led).turn_on() | |
.set_effect("Pulse") | |
.set_red(1.0) | |
.set_green(0.0) | |
.set_blue(0.0) | |
.set_brightness(0.8) | |
.perform(); | |
return; | |
} | |
// Not Connected -> Magenta | |
if (!id(api_id).is_connected()) { | |
id(status_led).turn_on() | |
.set_effect("Pulse") | |
.set_red(1.0) | |
.set_green(0.0) | |
.set_blue(1.0) | |
.set_brightness(0.3) | |
.perform(); | |
return; | |
} | |
// OK -> Blue | |
id(status_led).turn_on() | |
.set_effect("none") | |
.set_red(0.0) | |
.set_green(0.0) | |
.set_blue(1.0) | |
.set_brightness(0.3) | |
.perform(); | |
- delay: 1s | |
- id: update_plants_state | |
mode: queued | |
parameters: | |
relay_id: string | |
newState: string | |
then: | |
- lambda: |- | |
std::string plant1RelayId = id(plant1_relay).state; | |
std::string plant2RelayId = id(plant2_relay).state; | |
std::string plant3RelayId = id(plant3_relay).state; | |
boolean foundAMatch = false; | |
if (plant1RelayId == relay_id){ | |
id(plant1_status) = newState; | |
id(plant1_status_sensor).update(); | |
foundAMatch = true; | |
} | |
if (plant2RelayId == relay_id){ | |
id(plant2_status) = newState; | |
id(plant2_status_sensor).update(); | |
foundAMatch = true; | |
} | |
if (plant3RelayId == relay_id){ | |
id(plant3_status) = newState; | |
id(plant3_status_sensor).update(); | |
foundAMatch = true; | |
} | |
if (!foundAMatch){ | |
ESP_LOGW("plant_status_watering", "No Plant configured for: %s", relay_id.c_str()); | |
return; | |
} | |
- id: mark_plants_as_watered | |
mode: queued | |
parameters: | |
relay_id: string | |
then: | |
- lambda: |- | |
time_t now = id(esptime).now().timestamp; | |
std::string watered_state = std::string("watered"); | |
std::string plant1RelayId = id(plant1_relay).state; | |
std::string plant2RelayId = id(plant2_relay).state; | |
std::string plant3RelayId = id(plant3_relay).state; | |
boolean foundAMatch = false; | |
if (plant1RelayId == relay_id){ | |
id(plant1_status) = watered_state; | |
id(plant1_last_watered) = now; | |
id(plant1_status_sensor).update(); | |
id(plant1_last_watered_text_sensor).update(); | |
id(plant1_last_watered_timestamp_sensor).update(); | |
foundAMatch = true; | |
} | |
if (plant2RelayId == relay_id){ | |
id(plant2_status) = watered_state; | |
id(plant2_last_watered) = now; | |
id(plant2_status_sensor).update(); | |
id(plant2_last_watered_text_sensor).update(); | |
id(plant2_last_watered_timestamp_sensor).update(); | |
foundAMatch = true; | |
} | |
if (plant3RelayId == relay_id){ | |
id(plant3_status) = watered_state; | |
id(plant3_last_watered) = now; | |
id(plant3_status_sensor).update(); | |
id(plant3_last_watered_text_sensor).update(); | |
id(plant3_last_watered_timestamp_sensor).update(); | |
foundAMatch = true; | |
} | |
if (!foundAMatch){ | |
ESP_LOGW("mark_plants_as_watered", "No Plant configured for: %s", relay_id.c_str()); | |
return; | |
} | |
- id: schedule_watering | |
parameters: | |
relay_id: string | |
mode: queued | |
max_runs: 4 | |
then: | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: Schedule watering process" | |
args: | |
- relay_id.c_str() | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: Wait for system has booted" | |
args: | |
- relay_id.c_str() | |
- wait_until: | |
- lambda: "return id(has_booted) = true;" | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: Wait for all relays to finish" | |
args: | |
- relay_id.c_str() | |
- wait_until: | |
condition: | |
and: | |
- switch.is_off: relay1 | |
- switch.is_off: relay2 | |
- switch.is_off: relay3 | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: All other relays are off, proceeding with watering process" | |
args: | |
- relay_id.c_str() | |
- if: | |
condition: | |
lambda: |- | |
time_t now = id(esptime).now().timestamp; | |
int minWateringWaitTime = id(watering_wait_time_minutes).state * 60; | |
time_t last_watered = 0; | |
std::vector<std::string> plant_relays = { | |
id(plant1_relay).state, | |
id(plant2_relay).state, | |
id(plant3_relay).state | |
}; | |
std::vector<time_t> plant_last_watered = { | |
static_cast<time_t>(id(plant1_last_watered)), | |
static_cast<time_t>(id(plant2_last_watered)), | |
static_cast<time_t>(id(plant3_last_watered)) | |
}; | |
for (size_t i = 0; i < plant_relays.size(); i++) { | |
if (plant_relays[i] == relay_id && plant_last_watered[i] > last_watered) { | |
last_watered = plant_last_watered[i]; | |
} | |
} | |
return (now - last_watered) >= minWateringWaitTime; | |
then: | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: Wait for water tank" | |
args: | |
- relay_id.c_str() | |
- wait_until: | |
binary_sensor.is_off: water_tank_empty | |
- logger.log: | |
level: INFO | |
tag: schedule_watering | |
format: "%s: Water tank ready" | |
args: | |
- relay_id.c_str() | |
- lambda: |- | |
if (relay_id == "relay1") { | |
id(relay1).turn_on(); | |
} else if (relay_id == "relay2") { | |
id(relay2).turn_on(); | |
} else if (relay_id == "relay3") { | |
id(relay3).turn_on(); | |
} else { | |
ESP_LOGE("schedule_watering", "Unknown relay_id: %s", relay_id.c_str()); | |
} | |
else: | |
- logger.log: | |
level: WARN | |
tag: schedule_watering | |
format: "%s was watered too recently (last watered < %d min ago)" | |
args: | |
- relay_id.c_str() | |
- int(id(watering_wait_time_minutes).state) | |
- lambda: |- | |
std::string already_watered_state = std::string("already_watered"); | |
std::string plant1RelayId = id(plant1_relay).state; | |
std::string plant2RelayId = id(plant2_relay).state; | |
std::string plant3RelayId = id(plant3_relay).state; | |
boolean foundAMatch = false; | |
if (plant1RelayId == relay_id){ | |
foundAMatch = true; | |
id(plant1_status) = already_watered_state; | |
id(plant1_status_sensor).update(); | |
} | |
if (plant2RelayId == relay_id){ | |
foundAMatch = true; | |
id(plant2_status) = already_watered_state; | |
id(plant2_status_sensor).update(); | |
} | |
if (plant3RelayId == relay_id){ | |
foundAMatch = true; | |
id(plant3_status) = already_watered_state; | |
id(plant3_status_sensor).update(); | |
} | |
if (!foundAMatch){ | |
ESP_LOGW("schedule_watering", "No plant configured for: %s", relay_id.c_str()); | |
} | |
- id: relay_timer_script | |
parameters: | |
relay_id: string | |
mode: restart | |
then: | |
- logger.log: | |
level: INFO | |
tag: relay_timer_script | |
format: "Start watering using: %s" | |
args: | |
- relay_id.c_str() | |
- delay: !lambda "return id(max_watering_time).state * 1000;" | |
- logger.log: | |
level: INFO | |
tag: relay_timer_script | |
format: "Max Watering time reached. Turn off %s" | |
args: | |
- relay_id.c_str() | |
- lambda: |- | |
if (relay_id == "relay1") { | |
id(relay1).turn_off(); | |
} else if (relay_id == "relay2") { | |
id(relay2).turn_off(); | |
} else if (relay_id == "relay3") { | |
id(relay3).turn_off(); | |
} else { | |
ESP_LOGE("relay_timer_script", "Unknown relay_id: %s", relay_id.c_str()); | |
} | |
ota: | |
on_begin: | |
then: | |
- logger.log: "OTA started — prevent deep-sleep!" | |
- deep_sleep.prevent: sleep_control | |
on_end: | |
then: | |
- logger.log: "OTA ended — allow deep-sleep!" | |
- deep_sleep.allow: sleep_control | |
on_error: | |
then: | |
- logger.log: "OTA failed — allow deep-sleep!" | |
- deep_sleep.allow: sleep_control | |
time: | |
- platform: sntp | |
id: esptime | |
servers: | |
- 0.pool.ntp.org | |
- 1.pool.ntp.org | |
- 2.pool.ntp.org |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment