diff --git a/PLT-1/PLT-1.yaml b/PLT-1/PLT-1.yaml index 3f6c825..70c0cf5 100644 --- a/PLT-1/PLT-1.yaml +++ b/PLT-1/PLT-1.yaml @@ -35,7 +35,7 @@ blueprint: when the plant needs water domain: automation homeassistant: - min_version: "2024.6.0" + min_version: "2024.10.0" input: # ── Device ───────────────────────────────────────────────────────────────── @@ -168,15 +168,15 @@ blueprint: **Off**: the blueprint leaves the LED alone. **Flash On Alert**: the LED lights up for a few seconds each time a - low moisture alert fires, then turns itself off. Best choice for the - battery-powered PLT-1B. + low moisture alert fires, then turns itself off. Use this if you + have turned Prevent Sleep off to extend battery life. **Stay On Until Watered**: the LED stays lit until soil moisture - climbs back above your minimum. The LED can only be lit while the - device is awake, so this suits a wired PLT-1 with Prevent Sleep - turned on. On a sleeping device the firmware switches the LED off - every time it goes to sleep, and the repeat schedule lights it again - at the next reading. + climbs back above your minimum. Both the PLT-1 and the PLT-1B ship + with Prevent Sleep turned on, so this works out of the box on + either model. If you turn Prevent Sleep off, the firmware switches + the LED off every time the device sleeps, and it only relights + while the device is awake. default: "off" selector: select: @@ -581,15 +581,15 @@ trigger_variables: soil_temp_entity: >- {{ entities | select('search', 'soil_temperature') | list | first | default('') }} air_temp_entity: >- - {{ entities | select('search', 'aht_temperature') | list | first | default('') }} + {{ entities | select('search', 'air_temperature') | list | first | default('') }} air_humidity_entity: >- - {{ entities | select('search', 'aht_humidity') | list | first | default('') }} + {{ entities | select('search', 'air_humidity') | list | first | default('') }} light_entity: >- - {{ entities | select('search', 'ltr390light') | list | first | default('') }} + {{ entities | select('search', 'ltr390_light') | list | first | default('') }} uv_entity: >- - {{ entities | select('search', 'ltr390uvindex') | list | first | default('') }} + {{ entities | select('search', 'ltr390_uv_index') | list | first | default('') }} battery_entity: >- - {{ entities | select('search', 'batt_pct') | list | first | default('') }} + {{ entities | select('search', 'battery_level') | list | first | default('') }} rgb_light_entity: >- {{ device_entities(device_id) | select('match', 'light\..*') | list | first | default('') }} @@ -764,26 +764,41 @@ triggers: and states(battery_entity) | float < min_battery }} id: battery_min + # LED state sync. The template triggers above only fire on a false-to-true + # transition, so a threshold that is already breached when the automation + # loads would leave the LED dark until the next repeat. These triggers + # re-check the LED (and only the LED) after a restart or reload. + - trigger: homeassistant + event: start + id: led_sync + - trigger: event + event_type: automation_reloaded + id: led_sync + conditions: - condition: template - value_template: "{{ not disable_repeat or trigger.id != 'time' }}" + value_template: "{{ not disable_repeat or (trigger.id | default('manual')) != 'time' }}" action: - # Apply calibration voltages to device + # Apply calibration voltages to device. Skipped for led_sync, which fires on + # every automation reload system-wide. A sleeping device rejects these calls, + # so they must never abort the rest of the run. - if: - condition: template - value_template: "{{ dry_voltage_entity != '' }}" + value_template: "{{ dry_voltage_entity != '' and (trigger.id | default('manual')) != 'led_sync' }}" then: - action: number.set_value + continue_on_error: true target: entity_id: "{{ dry_voltage_entity }}" data: value: "{{ dry_voltage_value }}" - if: - condition: template - value_template: "{{ wet_voltage_entity != '' }}" + value_template: "{{ wet_voltage_entity != '' and (trigger.id | default('manual')) != 'led_sync' }}" then: - action: number.set_value + continue_on_error: true target: entity_id: "{{ wet_voltage_entity }}" data: @@ -797,34 +812,35 @@ action: {{ soil_moisture_enabled and soil_moisture_entity != '' and is_number(states(soil_moisture_entity)) and states(soil_moisture_entity) | float < min_soil_moisture - and trigger.id in ['time', 'soil_moisture_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'soil_moisture_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Soil Moisture Low" - message: >- - Soil moisture is {{ states(soil_moisture_entity) | float(0) | round(1) }}%, - below the minimum of {{ min_soil_moisture }}%. Time to water! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Soil Moisture Low" + message: >- + Soil moisture is {{ states(soil_moisture_entity) | float(0) | round(1) }}%, + below the minimum of {{ min_soil_moisture }}%. Time to water! + data: "{{ device_message_data }}" - alias: Light the RGB LED if: - condition: template value_template: "{{ led_mode != 'off' and rgb_light_entity != '' }}" then: - # The device may be asleep, so a failed call must not skip the custom action + # The device may be asleep, so a failed call must not skip the custom action. + # The data dict is rendered as one template so the Solid option's + # 'None' effect survives as a string instead of becoming Python None. - action: light.turn_on continue_on_error: true target: entity_id: "{{ rgb_light_entity }}" - data: - rgb_color: "{{ led_color }}" - brightness_pct: "{{ led_brightness }}" - effect: "{{ led_effect }}" + data: >- + {{ dict(rgb_color=led_color, brightness_pct=led_brightness, + effect=led_effect) }} - if: - condition: template value_template: "{{ led_mode == 'flash' }}" @@ -846,20 +862,20 @@ action: {{ soil_moisture_enabled and soil_moisture_entity != '' and is_number(states(soil_moisture_entity)) and states(soil_moisture_entity) | float > max_soil_moisture - and trigger.id in ['time', 'soil_moisture_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'soil_moisture_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Soil Moisture High" - message: >- - Soil moisture is {{ states(soil_moisture_entity) | float(0) | round(1) }}%, - above the maximum of {{ max_soil_moisture }}%. Risk of overwatering! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Soil Moisture High" + message: >- + Soil moisture is {{ states(soil_moisture_entity) | float(0) | round(1) }}%, + above the maximum of {{ max_soil_moisture }}%. Risk of overwatering! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -873,13 +889,55 @@ action: and is_number(states(soil_moisture_entity)) and states(soil_moisture_entity) | float >= min_soil_moisture and states(rgb_light_entity) == 'on' - and trigger.id in ['time', 'soil_moisture_ok'] }} + and (trigger.id | default('manual')) + in ['time', 'manual', 'soil_moisture_ok', 'led_sync'] }} then: - action: light.turn_off continue_on_error: true target: entity_id: "{{ rgb_light_entity }}" + # RGB LED: restore the correct LED state after a restart or reload. The + # template triggers only fire on a transition, so without this a threshold + # that is already breached when the automation loads never lights the LED. + # Notifications and the custom action deliberately do not run here. + - alias: RGB LED Sync + if: + - condition: template + value_template: >- + {{ (trigger.id | default('manual')) in ['led_sync', 'manual'] + and led_mode != 'off' and rgb_light_entity != '' + and soil_moisture_enabled and soil_moisture_entity != '' }} + then: + - choose: + # Hold mode is a state indicator, so it must relight to match reality + - conditions: + - condition: template + value_template: >- + {{ led_mode == 'hold' + and is_number(states(soil_moisture_entity)) + and states(soil_moisture_entity) | float < min_soil_moisture }} + sequence: + - action: light.turn_on + continue_on_error: true + target: + entity_id: "{{ rgb_light_entity }}" + data: >- + {{ dict(rgb_color=led_color, brightness_pct=led_brightness, + effect=led_effect) }} + # A sync must never start a flash: automation_reloaded fires on every + # automation save system-wide. An LED on in flash mode is a leftover + # from an interrupted flash, so clear it. + - conditions: + - condition: template + value_template: >- + {{ led_mode == 'flash' and states(rgb_light_entity) == 'on' }} + sequence: + - action: light.turn_off + continue_on_error: true + target: + entity_id: "{{ rgb_light_entity }}" + # Soil Temperature — Low - alias: Soil Temperature Low Alert if: @@ -888,21 +946,21 @@ action: {{ soil_temp_enabled and soil_temp_entity != '' and is_number(states(soil_temp_entity)) and states(soil_temp_entity) | float < min_soil_temp - and trigger.id in ['time', 'soil_temp_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'soil_temp_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Soil Too Cold" - message: >- - Soil temperature is {{ states(soil_temp_entity) | float(0) | round(1) }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}, - below the minimum of {{ min_soil_temp }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}. - Roots may be too cold! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Soil Too Cold" + message: >- + Soil temperature is {{ states(soil_temp_entity) | float(0) | round(1) }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}, + below the minimum of {{ min_soil_temp }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}. + Roots may be too cold! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -914,21 +972,21 @@ action: {{ soil_temp_enabled and soil_temp_entity != '' and is_number(states(soil_temp_entity)) and states(soil_temp_entity) | float > max_soil_temp - and trigger.id in ['time', 'soil_temp_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'soil_temp_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Soil Too Hot" - message: >- - Soil temperature is {{ states(soil_temp_entity) | float(0) | round(1) }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}, - above the maximum of {{ max_soil_temp }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}. - Roots may overheat! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Soil Too Hot" + message: >- + Soil temperature is {{ states(soil_temp_entity) | float(0) | round(1) }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}, + above the maximum of {{ max_soil_temp }}{{ state_attr(soil_temp_entity, 'unit_of_measurement') }}. + Roots may overheat! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -940,21 +998,21 @@ action: {{ air_temp_enabled and air_temp_entity != '' and is_number(states(air_temp_entity)) and states(air_temp_entity) | float < min_air_temp - and trigger.id in ['time', 'air_temp_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'air_temp_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Air Too Cold" - message: >- - Air temperature is {{ states(air_temp_entity) | float(0) | round(1) }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}, - below the minimum of {{ min_air_temp }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}. - Environment may be too cold! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Air Too Cold" + message: >- + Air temperature is {{ states(air_temp_entity) | float(0) | round(1) }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}, + below the minimum of {{ min_air_temp }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}. + Environment may be too cold! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -966,21 +1024,21 @@ action: {{ air_temp_enabled and air_temp_entity != '' and is_number(states(air_temp_entity)) and states(air_temp_entity) | float > max_air_temp - and trigger.id in ['time', 'air_temp_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'air_temp_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Air Too Hot" - message: >- - Air temperature is {{ states(air_temp_entity) | float(0) | round(1) }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}, - above the maximum of {{ max_air_temp }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}. - Environment may be too hot! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Air Too Hot" + message: >- + Air temperature is {{ states(air_temp_entity) | float(0) | round(1) }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}, + above the maximum of {{ max_air_temp }}{{ state_attr(air_temp_entity, 'unit_of_measurement') }}. + Environment may be too hot! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -992,21 +1050,21 @@ action: {{ air_humidity_enabled and air_humidity_entity != '' and is_number(states(air_humidity_entity)) and states(air_humidity_entity) | float < min_air_humidity - and trigger.id in ['time', 'air_humidity_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'air_humidity_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Air Too Dry" - message: >- - Air humidity is {{ states(air_humidity_entity) | float(0) | round(1) }}%, - below the minimum of {{ min_air_humidity }}%. - Dry air can stress your plant! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Air Too Dry" + message: >- + Air humidity is {{ states(air_humidity_entity) | float(0) | round(1) }}%, + below the minimum of {{ min_air_humidity }}%. + Dry air can stress your plant! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -1018,21 +1076,21 @@ action: {{ air_humidity_enabled and air_humidity_entity != '' and is_number(states(air_humidity_entity)) and states(air_humidity_entity) | float > max_air_humidity - and trigger.id in ['time', 'air_humidity_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'air_humidity_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Humidity Too High" - message: >- - Air humidity is {{ states(air_humidity_entity) | float(0) | round(1) }}%, - above the maximum of {{ max_air_humidity }}%. - Risk of mold or rot! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Humidity Too High" + message: >- + Air humidity is {{ states(air_humidity_entity) | float(0) | round(1) }}%, + above the maximum of {{ max_air_humidity }}%. + Risk of mold or rot! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -1044,21 +1102,21 @@ action: {{ light_enabled and light_entity != '' and is_number(states(light_entity)) and states(light_entity) | float < min_light - and trigger.id in ['time', 'light_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'light_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Not Enough Light" - message: >- - Light level is {{ states(light_entity) | float(0) | round(0) }} lux, - below the minimum of {{ min_light }} lux. - Move to a brighter spot! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Not Enough Light" + message: >- + Light level is {{ states(light_entity) | float(0) | round(0) }} lux, + below the minimum of {{ min_light }} lux. + Move to a brighter spot! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -1070,21 +1128,21 @@ action: {{ light_enabled and light_entity != '' and is_number(states(light_entity)) and states(light_entity) | float > max_light - and trigger.id in ['time', 'light_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'light_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Too Much Light" - message: >- - Light level is {{ states(light_entity) | float(0) | round(0) }} lux, - above the maximum of {{ max_light }} lux. - Direct sun may scorch leaves! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Too Much Light" + message: >- + Light level is {{ states(light_entity) | float(0) | round(0) }} lux, + above the maximum of {{ max_light }} lux. + Direct sun may scorch leaves! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -1096,21 +1154,21 @@ action: {{ uv_enabled and uv_entity != '' and is_number(states(uv_entity)) and states(uv_entity) | float > max_uv - and trigger.id in ['time', 'uv_max'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'uv_max'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — UV Index High" - message: >- - UV index is {{ states(uv_entity) | float(0) | round(1) }}, - above the maximum of {{ max_uv }}. - Excessive UV exposure! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — UV Index High" + message: >- + UV index is {{ states(uv_entity) | float(0) | round(1) }}, + above the maximum of {{ max_uv }}. + Excessive UV exposure! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action @@ -1122,21 +1180,21 @@ action: {{ battery_enabled and battery_entity != '' and is_number(states(battery_entity)) and states(battery_entity) | float < min_battery - and trigger.id in ['time', 'battery_min'] }} + and (trigger.id | default('manual')) in ['time', 'manual', 'battery_min'] }} then: - if: - condition: template value_template: "{{ notify_device is not none and notify_device != '' }}" then: - - device_id: !input notify_device - domain: mobile_app - type: notify - title: "{{ plant_name }} — Sensor Battery Low" - message: >- - Sensor battery is {{ states(battery_entity) | float(0) | round(0) }}%, - below the minimum of {{ min_battery }}%. - Please recharge! - data: "{{ device_message_data }}" + - action: "notify.mobile_app_{{ device_attr(notify_device, 'name') | slugify }}" + continue_on_error: true + data: + title: "{{ plant_name }} — Sensor Battery Low" + message: >- + Sensor battery is {{ states(battery_entity) | float(0) | round(0) }}%, + below the minimum of {{ min_battery }}%. + Please recharge! + data: "{{ device_message_data }}" - choose: [] default: !input custom_action diff --git a/PLT-1/README.md b/PLT-1/README.md index 8e0af48..7bc023a 100644 --- a/PLT-1/README.md +++ b/PLT-1/README.md @@ -25,7 +25,7 @@ Each sensor group is **disabled by default** (except Soil Moisture) so you can m ## Requirements - **Apollo Automation PLT-1 or PLT-1B** connected to Home Assistant via ESPHome -- Home Assistant **2024.6.0** or newer +- Home Assistant **2024.10.0** or newer - Home Assistant Companion App installed on your phone *(for mobile push notifications)* --- @@ -43,11 +43,19 @@ Each sensor group is **disabled by default** (except Soil Moisture) so you can m 5. Select your **mobile device** for push notifications (optional — you can use a Custom Action instead) -6. Expand the sensor groups you want to monitor, enable them, and adjust the thresholds to suit your plant +6. Expand **Calibration** and enter your sensor's dry and wet voltage readings. + To get them: temporarily enable the **Soil ADC** diagnostic entity on your device, + record the voltage with the probe dry in air, then again with the probe submerged + in water up to the Apollo label, and disable Soil ADC again. See the + [calibration guide](https://wiki.apolloautomation.com/products/general/calibrating-and-updating/calibrate-plt1). + The automation writes these values to the device on every alert and repeat run, + so they survive factory resets and firmware updates. -7. *(Optional)* Expand **Repeat Alerts** to configure how often you get reminded while a condition persists +7. Expand the sensor groups you want to monitor, enable them, and adjust the thresholds to suit your plant -8. *(Optional)* Expand **RGB LED Indicator** and pick a mode to light the sensor's onboard LED when the plant needs water +8. *(Optional)* Expand **Repeat Alerts** to configure how often you get reminded while a condition persists + +9. *(Optional)* Expand **RGB LED Indicator** and pick a mode to light the sensor's onboard LED when the plant needs water --- @@ -73,10 +81,22 @@ Each sensor group is **disabled by default** (except Soil Moisture) so you can m | Input | Description | Default | |---|---|---| +| Disable Repeat Alerts | Fire only once per threshold crossing, never on a schedule | Off | | Repeat — Hours | How often to re-alert while condition persists | Every Hour | | Repeat — Minutes | Minute offset for repeat schedule | At :01 | -Set **Hours** to `Never (disable repeat)` to receive only one alert per threshold crossing. +Turn on **Disable Repeat Alerts** to receive only one alert per threshold crossing. + +### Calibration + +Enter the soil probe's calibration voltages here. The automation writes them to the +device's `Dry Voltage` and `100% Water Voltage` entities on every alert and repeat +run, so a factory reset or firmware update cannot silently wipe your calibration. + +| Input | Description | Default | +|---|---|---| +| Dry Voltage (V) | Soil ADC reading with the probe dry in air | 2.77 | +| Wet Voltage (V) | Soil ADC reading with the probe submerged up to the Apollo label | 1.47 | ### RGB LED Indicator @@ -92,12 +112,14 @@ Lights the PLT-1's onboard RGB LED when soil moisture drops below your minimum, **Deep sleep matters here.** The PLT-1 firmware turns the LED off every time the device goes to sleep, so the LED is only lit while the device is awake: -- On a **wired PLT-1**, the `Prevent Sleep` switch is on out of the box, so the device stays awake and `Stay On Until Watered` holds the LED until moisture recovers. -- On a **battery PLT-1B**, use `Flash On Alert`. The alert fires the moment the device wakes and reports, so the flash lands inside the wake window. `Stay On Until Watered` will relight the LED at each reading, but it goes dark for the sleep interval in between. -- The blueprint never changes the `Prevent Sleep` switch. If you want a PLT-1B to hold the LED, you have to turn that switch on yourself and accept the battery cost. +- **Both the wired PLT-1 and the battery PLT-1B ship with `Prevent Sleep` turned on**, so out of the box the device stays awake and `Stay On Until Watered` holds the LED until moisture recovers on either model. +- If you turn `Prevent Sleep` off to extend PLT-1B battery life, the device deep sleeps and the firmware switches the LED off each time it goes down. Use `Flash On Alert` then, because the alert fires while the device is still awake and reporting, so the flash lands. `Stay On Until Watered` looks broken on a sleeping device, since the LED only relights while the device happens to be awake. +- The blueprint never changes the `Prevent Sleep` switch in either direction. With `Stay On Until Watered`, the LED is switched off once soil moisture climbs back above your minimum. +After a Home Assistant restart or an automation reload, the blueprint re-checks soil moisture and restores the LED in `Stay On Until Watered` mode, so a threshold that was already breached beforehand does not leave the LED dark until the next repeat. `Flash On Alert` is never re-flashed by a restart, because a flash marks the moment an alert fires rather than an ongoing state. + ### Per-Sensor Inputs Each sensor group (Soil Moisture, Soil Temperature, Air Temperature, Air Humidity, Light Intensity, UV Index, Battery) shares the same pattern: @@ -107,13 +129,12 @@ Each sensor group (Soil Moisture, Soil Temperature, Air Temperature, Air Humidit | Enable Alerts | Toggle — enable or disable this sensor group | | Minimum threshold | Alert when value drops below this level | | Maximum threshold | Alert when value rises above this level *(where applicable)* | -| Alert Delay (minutes) | How long the condition must persist before firing — prevents false alerts | --- ## Alert Behaviour -- **Immediate alert**: fires as soon as the threshold is exceeded for the configured delay duration +- **Immediate alert**: fires as soon as a sensor reading crosses the threshold - **Repeat alert**: the repeat schedule re-sends the alert while the condition persists - **Deduplication**: the automation is set to `parallel` mode, so multiple sensors can alert simultaneously without blocking each other