Skip to content

Instantly share code, notes, and snippets.

@pavax
Last active May 9, 2025 23:41
Show Gist options
  • Save pavax/7db3cd531fd94854c609c8274d4b8136 to your computer and use it in GitHub Desktop.
Save pavax/7db3cd531fd94854c609c8274d4b8136 to your computer and use it in GitHub Desktop.
ESPHome Config for Plant Watering
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:
- script.execute: measure_battery
- light.turn_on:
id: status_led
brightness: 30%
transition_length: 0s
blue: 1
red: 1
green: 1
- delay: 2s
- script.execute: led_status_light
- priority: -100
then:
- lambda: "id(has_booted) = true;"
on_shutdown:
- priority: -200.0
then:
- light.turn_off:
id: status_led
transition_length: 0s
- 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: INFO
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: plant4_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: plant4_last_watered
type: unsigned long
restore_value: yes
initial_value: "0"
- id: has_booted
type: bool
restore_value: no
initial_value: "false"
- id: all_plants_scanned
type: bool
restore_value: no
initial_value: "false"
- id: elapsed_watering_time
type: int
restore_value: no
initial_value: "0"
#web_server:
# port: 80
interval:
- interval: 2sec
id: "stop_scanning_interval"
startup_delay: 5sec
then:
- if:
condition:
- and:
- lambda: "return !isnan(id(plant1_sensor_moisture).state);"
- lambda: "return !isnan(id(plant2_sensor_moisture).state);"
- lambda: "return !isnan(id(plant3_sensor_moisture).state);"
- lambda: "return !isnan(id(plant4_sensor_moisture).state);"
then:
- lambda: |-
static bool executed = false;
if (!executed) {
ESP_LOGI("main", "Stop BLE Scanning since all plants moisture levels have been fetched.");
id(all_plants_scanned) = true;
id(ble_tracker).stop_scan();
id(led_status_light).execute();
executed = true;
}
- interval: 5s
id: "max_running_time_reached_interval"
startup_delay: 60s
then:
- if:
condition:
- and:
- lambda: "return id(uptime_seconds).state >= (id(max_running_time_minutes).state * 60);"
- not:
- script.is_running: watering_process
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
- script.execute: prepare_shutdown
- interval: 5sec
id: "duty_done_interval"
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("recently_watered");'
- lambda: 'return id(plant2_status) == std::string("ok") || id(plant2_status) == std::string("watered") || id(plant2_status) == std::string("recently_watered");'
- lambda: 'return id(plant3_status) == std::string("ok") || id(plant3_status) == std::string("watered") || id(plant3_status) == std::string("recently_watered");'
- lambda: 'return id(plant4_status) == std::string("ok") || id(plant4_status) == std::string("watered") || id(plant4_status) == std::string("recently_watered");'
- lambda: "return id(all_plants_scanned);"
then:
- esp32_ble_tracker.stop_scan
- if:
condition:
switch.is_off: prevent_deep_sleep_switch
then:
- logger.log:
level: INFO
format: "Everything is done - Prepare Deep Sleep!"
- script.execute: prepare_shutdown
light:
- platform: neopixelbus
id: status_led
type: GRB
pin: GPIO7
num_leds: 1
name: "Onboard RGB"
variant: ws2812
effects:
- pulse:
name: "Pulse"
transition_length: 500ms
update_interval: 500ms
- pulse:
name: "Breathing"
transition_length: 2000ms
update_interval: 2000ms
min_brightness: 20%
max_brightness: 100%
output:
- platform: gpio
id: battery_gnd
pin:
number: GPIO2
inverted: true
mode:
output: true
pullup: false
pulldown: false
ignore_strapping_warning: true
binary_sensor:
- platform: gpio
id: water_tank_empty
name: "Water Tank"
icon: "mdi:bucket"
# 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"
- script.execute:
id: set_relay_state
relay_id: "any"
state: "OFF"
use_internal: false
sensor:
- platform: adc
pin: GPIO3
name: "Battery Voltage"
id: battery_voltage
icon: "mdi:flash"
attenuation: 12db
accuracy_decimals: 2
update_interval: never
filters:
- median:
window_size: 5
send_every: 5
send_first_at: 1
- 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"
id: plant1_sensor_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"
id: plant2_sensor_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"
id: plant3_sensor_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: xiaomi_hhccjcy01
mac_address: "C4:7C:8D:6B:96:05"
moisture:
name: "Plant 4 Soil Moisture"
id: plant4_sensor_moisture
on_value:
then:
- logger.log:
level: INFO
format: "Plant 4: Moisture received: %.1f"
args: [x]
- if:
condition:
and:
- lambda: 'return id(plant4_status) == std::string("unknown");'
- lambda: "return x < id(plant_4_min_moisture_level).state;"
then:
- logger.log:
level: WARN
format: "Plant 4: Soil dry - Schedule watering!"
- globals.set:
id: plant4_status
value: '"needs_water"'
- script.execute:
id: schedule_watering
relay_id: !lambda "return id(plant4_relay).state;"
- if:
condition:
and:
- lambda: 'return id(plant4_status) == std::string("unknown");'
- lambda: "return x > id(plant_4_min_moisture_level).state;"
then:
- logger.log:
level: INFO
format: "Plant 4: Soil moisture OK — no watering needed."
- globals.set:
id: plant4_status
value: '"ok"'
- component.update: plant4_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);
- platform: template
name: "Plant 4 Last Watered Timestamp"
id: plant4_last_watered_timestamp_sensor
unit_of_measurement: "s"
accuracy_decimals: 0
entity_category: diagnostic
update_interval: never
lambda: return id(plant4_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
icon: "mdi:flower"
lambda: return id(plant1_status);
- platform: template
id: plant2_status_sensor
name: "Plant-2 Status"
update_interval: never
icon: "mdi:flower"
lambda: return id(plant2_status);
- platform: template
id: plant3_status_sensor
name: "Plant-3 Status"
update_interval: never
icon: "mdi:flower"
lambda: return id(plant3_status);
- platform: template
id: plant4_status_sensor
name: "Plant-4 Status"
update_interval: never
icon: "mdi:flower"
lambda: return id(plant4_status);
- platform: template
name: "Plant 1 Last Watered"
id: plant1_last_watered_text_sensor
entity_category: diagnostic
update_interval: never
icon: "mdi:flower-pollen"
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
icon: "mdi:flower-pollen"
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
icon: "mdi:flower-pollen"
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);
}
- platform: template
name: "Plant 4 Last Watered"
id: plant4_last_watered_text_sensor
entity_category: diagnostic
update_interval: never
icon: "mdi:flower-pollen"
lambda: |-
if (id(plant4_last_watered) == 0) {
return std::string("Never");
} else {
time_t last_time = id(plant4_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;"
icon: "mdi:water-pump"
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: watering_process
relay_id: "relay1"
else:
- logger.log:
level: WARN
format: "Blocking relay1 activation!"
turn_off_action:
- script.stop: watering_process
- switch.turn_off: relay1_internal
- if:
condition:
- lambda: "return id(has_booted);"
then:
- script.execute:
id: update_plants_state
relay_id: "relay1"
newState: "watered"
update_timestamp: true
- platform: template
name: "Relay 2"
id: relay2
lambda: "return id(relay2_internal).state;"
icon: "mdi:water-pump"
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: watering_process
relay_id: "relay2"
else:
- logger.log:
level: WARN
format: "Blocking relay2 activation!"
turn_off_action:
- switch.turn_off: relay2_internal
- script.stop: watering_process
- if:
condition:
- lambda: "return id(has_booted);"
then:
- script.execute:
id: update_plants_state
relay_id: "relay2"
newState: "watered"
update_timestamp: true
- platform: template
name: "Relay 3"
id: relay3
lambda: "return id(relay3_internal).state;"
icon: "mdi:water-pump"
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: watering_process
relay_id: "relay3"
else:
- logger.log:
level: WARN
format: "Blocking relay3 activation!"
turn_off_action:
- switch.turn_off: relay3_internal
- script.stop: watering_process
- if:
condition:
- lambda: "return id(has_booted);"
then:
- script.execute:
id: update_plants_state
relay_id: "relay3"
newState: "watered"
update_timestamp: true
- 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:
number: GPIO8
ignore_strapping_warning: true
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:
- script.execute:
id: set_relay_state
relay_id: "any"
state: "OFF"
use_internal: false
- platform: template
id: prevent_deep_sleep_switch
name: "Prevent Deep Sleep"
restore_mode: RESTORE_DEFAULT_OFF
optimistic: true
entity_category: config
icon: "mdi:sleep-off"
on_turn_on:
then:
- logger.log:
level: DEBUG
format: "Preventing Deep Sleep"
- deep_sleep.prevent: sleep_control
on_turn_off:
then:
- logger.log:
level: DEBUG
format: "Allowing Deep Sleep"
- deep_sleep.allow: sleep_control
number:
- platform: template
name: "Watering Pulse Time (ON)"
id: watering_pulse_time_on
unit_of_measurement: "sec"
entity_category: config
min_value: 0
max_value: 120
step: 1
optimistic: true
mode: box
initial_value: 0
restore_value: yes
- platform: template
name: "Watering Pulse Time (OFF)"
id: watering_pulse_time_off
unit_of_measurement: "sec"
entity_category: config
min_value: 0
max_value: 120
step: 1
optimistic: true
mode: box
initial_value: 0
restore_value: yes
- 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: "Total Watering Time"
id: total_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: "Re-watering wait time"
id: rewatering_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
- platform: template
name: "Plant 4 min. Moisture Level"
id: plant_4_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: "relay1"
restore_value: yes
optimistic: true
- platform: template
name: "Plant-3"
id: plant3_relay
options:
- "relay1"
- "relay2"
- "relay3"
entity_category: config
initial_option: "relay1"
restore_value: yes
optimistic: true
- platform: template
name: "Plant-4"
id: plant4_relay
options:
- "relay1"
- "relay2"
- "relay3"
entity_category: config
initial_option: "relay2"
restore_value: yes
optimistic: true
api:
on_client_connected:
- script.execute: led_status_light
- esp32_ble_tracker.start_scan:
continuous: true
- component.update: plant1_last_watered_timestamp_sensor
- component.update: plant2_last_watered_timestamp_sensor
- component.update: plant3_last_watered_timestamp_sensor
- component.update: plant4_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
- component.update: plant4_last_watered_text_sensor
on_client_disconnected:
- script.execute: led_status_light
- esp32_ble_tracker.stop_scan:
mqtt:
broker: !secret mqtt_broker
username: !secret mqtt_username
password: !secret mqtt_password
discovery: false
topic_prefix: null
on_message:
- topic: ${name}/schedule-watering
qos: 1
payload: "plant1"
then:
- script.execute:
id: schedule_watering
relay_id: !lambda "return id(plant1_relay).state;"
- mqtt.publish:
topic: ${name}/schedule-watering
payload: "ACK"
retain: true
- topic: ${name}/schedule-watering
qos: 1
payload: "plant4"
then:
- script.execute:
id: schedule_watering
relay_id: !lambda "return id(plant4_relay).state;"
- mqtt.publish:
topic: ${name}/schedule-watering
payload: "ACK"
retain: true
- topic: ${name}/deep-sleep
qos: 1
payload: "ON"
then:
- switch.turn_off: prevent_deep_sleep_switch
- mqtt.publish:
topic: ${name}/deep-sleep
payload: "ACK"
retain: true
- topic: ${name}/deep-sleep
qos: 1
payload: "OFF"
then:
- switch.turn_on: prevent_deep_sleep_switch
- mqtt.publish:
topic: ${name}/deep-sleep
payload: "ACK"
retain: true
deep_sleep:
id: sleep_control
esp32_ble_tracker:
id: 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
script:
- id: measure_battery
mode: single
then:
- output.turn_on: battery_gnd
- repeat:
count: 20
then:
- delay: 25ms
- component.update: battery_voltage
- output.turn_off: battery_gnd
- id: set_relay_state
mode: restart
parameters:
relay_id: string
state: string
use_internal: bool
then:
- lambda: |-
std::string state_lc = state;
std::transform(state_lc.begin(), state_lc.end(), state_lc.begin(), ::tolower);
bool handled = false;
// Relay 1
if (relay_id == "relay1") {
handled = true;
if (use_internal) {
if (state_lc == "on" && !id(relay1_internal).state) {
id(relay1_internal).turn_on();
} else if (state_lc == "off" && id(relay1_internal).state) {
id(relay1_internal).turn_off();
}
} else {
if (state_lc == "on" && !id(relay1).state) {
id(relay1).turn_on();
} else if (state_lc == "off" && id(relay1).state) {
id(relay1).turn_off();
}
}
}
// Relay 2
else if (relay_id == "relay2") {
handled = true;
if (use_internal) {
if (state_lc == "on" && !id(relay2_internal).state) {
id(relay2_internal).turn_on();
} else if (state_lc == "off" && id(relay2_internal).state) {
id(relay2_internal).turn_off();
}
} else {
if (state_lc == "on" && !id(relay2).state) {
id(relay2).turn_on();
} else if (state_lc == "off" && id(relay2).state) {
id(relay2).turn_off();
}
}
}
// Relay 3
else if (relay_id == "relay3") {
handled = true;
if (use_internal) {
if (state_lc == "on" && !id(relay3_internal).state) {
id(relay3_internal).turn_on();
} else if (state_lc == "off" && id(relay3_internal).state) {
id(relay3_internal).turn_off();
}
} else {
if (state_lc == "on" && !id(relay3).state) {
id(relay3).turn_on();
} else if (state_lc == "off" && id(relay3).state) {
id(relay3).turn_off();
}
}
}
// "any"
else if (relay_id == "any") {
handled = true;
if (use_internal) {
if (state_lc == "on") {
if (!id(relay1_internal).state) id(relay1_internal).turn_on();
if (!id(relay2_internal).state) id(relay2_internal).turn_on();
if (!id(relay3_internal).state) id(relay3_internal).turn_on();
} else if (state_lc == "off") {
if (id(relay1_internal).state) id(relay1_internal).turn_off();
if (id(relay2_internal).state) id(relay2_internal).turn_off();
if (id(relay3_internal).state) id(relay3_internal).turn_off();
}
} else {
if (state_lc == "on") {
if (!id(relay1).state) id(relay1).turn_on();
if (!id(relay2).state) id(relay2).turn_on();
if (!id(relay3).state) id(relay3).turn_on();
} else if (state_lc == "off") {
if (id(relay1).state) id(relay1).turn_off();
if (id(relay2).state) id(relay2).turn_off();
if (id(relay3).state) id(relay3).turn_off();
}
}
}
if (!handled) {
ESP_LOGW("set_relay_state", "Unknown relay_id: %s", relay_id.c_str());
}
- id: prepare_shutdown
mode: single
then:
- light.turn_off:
id: status_led
transition_length: 0s
- component.update: uptime_seconds
- component.update: uptime_text_sensor
- delay: 2s
- deep_sleep.allow: sleep_control
- deep_sleep.enter:
id: sleep_control
sleep_duration: !lambda "return id(sleep_duration_minutes).state * 1000 * 60;"
- id: led_status_light
mode: restart
then:
- lambda: |-
// Only proceed if status_led is currently on
if (!id(status_led).current_values.is_on()) {
return;
}
// 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(1.0)
.set_transition_length(0)
.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(1.0)
.set_transition_length(0)
.perform();
return;
}
// All Plants Scanned -> Cyab
if (id(all_plants_scanned)){
id(status_led).turn_on()
.set_effect("Breathing")
.set_red(0.0)
.set_green(1.0)
.set_blue(0.8)
.set_brightness(1.0)
.set_transition_length(0)
.perform();
return;
}
// OK -> Blue
id(status_led).turn_on()
.set_effect("Breathing")
.set_red(0.0)
.set_green(0.0)
.set_blue(1.0)
.set_brightness(1.0)
.perform();
- id: update_plants_state
mode: queued
parameters:
relay_id: string
newState: string
update_timestamp: boolean
then:
- lambda: |-
std::string plant1RelayId = id(plant1_relay).state;
std::string plant2RelayId = id(plant2_relay).state;
std::string plant3RelayId = id(plant3_relay).state;
std::string plant4RelayId = id(plant4_relay).state;
time_t now = id(esptime).now().timestamp;
boolean foundAMatch = false;
if (plant1RelayId == relay_id){
foundAMatch = true;
id(plant1_status) = newState;
id(plant1_status_sensor).update();
if (update_timestamp){
id(plant1_last_watered) = now;
id(plant1_last_watered_text_sensor).update();
id(plant1_last_watered_timestamp_sensor).update();
}
}
if (plant2RelayId == relay_id){
foundAMatch = true;
id(plant2_status) = newState;
id(plant2_status_sensor).update();
if (update_timestamp){
id(plant2_last_watered) = now;
id(plant2_last_watered_text_sensor).update();
id(plant2_last_watered_timestamp_sensor).update();
}
}
if (plant3RelayId == relay_id){
foundAMatch = true;
id(plant3_status) = newState;
id(plant3_status_sensor).update();
if (update_timestamp){
id(plant3_last_watered) = now;
id(plant3_last_watered_text_sensor).update();
id(plant3_last_watered_timestamp_sensor).update();
}
}
if (plant4RelayId == relay_id){
foundAMatch = true;
id(plant4_status) = newState;
id(plant4_status_sensor).update();
if (update_timestamp){
id(plant4_last_watered) = now;
id(plant4_last_watered_text_sensor).update();
id(plant4_last_watered_timestamp_sensor).update();
}
}
if (!foundAMatch){
ESP_LOGW("update_plants_state", "No Plant configured for: %s", relay_id.c_str());
}
- 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 rewateringWaitTime = id(rewatering_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,
id(plant4_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)),
static_cast<time_t>(id(plant4_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) >= rewateringWaitTime;
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()
- script.execute:
id: set_relay_state
relay_id: !lambda "return relay_id;"
state: "ON"
use_internal: false
else:
- logger.log:
level: WARN
tag: schedule_watering
format: "%s was watered recently (last watered < %d min ago)"
args:
- relay_id.c_str()
- int(id(rewatering_wait_time_minutes).state)
- script.execute:
id: update_plants_state
relay_id: !lambda "return relay_id;"
newState: "recently_watered"
update_timestamp: false
- id: watering_process
parameters:
relay_id: string
mode: single
then:
- logger.log:
level: INFO
tag: watering_process
format: "Start watering using: %s"
args:
- relay_id.c_str()
- script.execute:
id: update_plants_state
relay_id: !lambda "return relay_id;"
newState: "watering"
update_timestamp: false
- if:
condition:
- lambda: "return id(watering_pulse_time_on).state > 0 && id(watering_pulse_time_off).state > 0 ;"
then:
- logger.log:
level: INFO
tag: watering_process
format: "Use watering pulse with ON: %0.0fs / OFF: %0.0fs"
args:
- id(watering_pulse_time_on).state
- id(watering_pulse_time_off).state
- while:
condition:
- lambda: "return ( id(total_watering_time).state * 1000 ) > id(elapsed_watering_time);"
then:
- script.execute:
id: set_relay_state
relay_id: !lambda "return relay_id;"
state: "ON"
use_internal: true
- delay: !lambda "return id(watering_pulse_time_on).state * 1000;"
- script.execute:
id: set_relay_state
relay_id: !lambda "return relay_id;"
state: "OFF"
use_internal: true
- lambda: |-
id(elapsed_watering_time) += ( id(watering_pulse_time_on).state * 1000 );
- if:
condition:
lambda: |-
// Check if there's enough time left for another full cycle
return id(total_watering_time).state * 1000 > id(elapsed_watering_time);
then:
- delay: !lambda "return id(watering_pulse_time_off).state * 1000;"
else:
- logger.log:
level: INFO
tag: watering_process
format: "Use continous watering for %0.0fs"
args: id(total_watering_time).state
- script.execute:
id: set_relay_state
relay_id: !lambda "return relay_id;"
state: "ON"
use_internal: true
- delay: !lambda "return id(total_watering_time).state * 1000;"
- logger.log:
level: INFO
tag: watering_process
format: "Total watering time reached."
- script.execute:
id: set_relay_state
relay_id: !lambda "return relay_id;"
state: "OFF"
use_internal: false
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