Skip to content

Instantly share code, notes, and snippets.

@jgillula
Last active December 25, 2023 12:27
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jgillula/787c5acfbd9a9a725a290dad8c4eae24 to your computer and use it in GitHub Desktop.
Save jgillula/787c5acfbd9a9a725a290dad8c4eae24 to your computer and use it in GitHub Desktop.
blueprint:
name: Advanced Appliance Power Monitor
description: |
Monitors the activity of an appliance based on the power it uses (W) after given delays, and triggers actions based on start, running (power usage changed), and stop. You can use the variables
* `{{ elapsed_time }}` (total seconds)
* `{{ elapsed_time_days }}`
* `{{ elapsed_time_hours }}`
* `{{ elapsed_time_minutes }}`
* `{{ elapsed_time_seconds }}`
* `{{ energy_used }}`
* `{{ event_type }}` (start, running, or stop)
* `{{ start_threshold }}`
* `{{ start_time }}` (a unix timestamp)
* `{{ finish_threshold }}`
in all the steps (start, running, and stop).
domain: automation
input:
power_entity:
name: Power Entity
description: Entity representing the power consumption of the appliance (W).
selector:
entity:
domain: sensor
device_class: power
energy_entity:
name: Energy Entity
description: (*Optional*) Entity representing the energy consumption of the appliance.
selector:
entity:
domain: sensor
device_class: energy
default: null
start_threshold:
name: Starting Threshold
description: Power threshold above which we assume the appliance has started.
default: 1
selector:
number:
min: 0
max: 100000
unit_of_measurement: "W"
mode: box
finish_threshold:
name: Finishing Threshold
description: Power threshold below which we assume the appliance has finished.
default: 1
selector:
number:
min: 0
max: 100000
unit_of_measurement: "W"
mode: box
start_threshold_delay:
name: Starting Threshold Delay
description: (*Optional*) How long the appliance power must be above Starting Threshold before triggering the Start Action.
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration:
running_threshold_delay:
name: Running Threshold Delay
description: |
(*Optional*) How long the appliance power must have changed before triggering the Running Action.
***You must set this larger than Finishing Threshold Delay below. If you don't it will automatically be set 1 second greater than Finishing Delay for you.***
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration:
finish_threshold_delay:
name: Finishing Threshold Delay
description: (*Optional*) How long the appliance power must be below Finishing Threshold before triggering the Stop Action.
default:
hours: 0
minutes: 0
seconds: 0
selector:
duration:
on_start:
name: Start Action
description: (*Optional*) Actions to perform when the appliance starts.
default: []
selector:
action: {}
on_running:
name: Running Action
description: (*Optional*) Actions to perform when the appliance is running.
default: []
selector:
action: {}
on_stop:
name: Stop Action
description: (*Optional*) Actions to perform when the appliance stops.
default: []
selector:
action: {}
trigger:
- platform: numeric_state
entity_id: !input "power_entity"
for: !input "start_threshold_delay"
above: !input "start_threshold"
action:
- variables:
start_threshold: !input "start_threshold"
finish_threshold: !input "finish_threshold"
start_threshold_delay: !input "start_threshold_delay"
running_threshold_delay: !input "running_threshold_delay"
finish_threshold_delay: !input "finish_threshold_delay"
start_time: "{{ as_timestamp(now()) - start_threshold_delay.hours * 3600 - start_threshold_delay.minutes * 60 - start_threshold_delay.seconds }}"
energy_entity: !input "energy_entity"
initial_energy: >
{% if energy_entity is not none %}
{{ states(energy_entity) | float }}
{% else %}
0
{% endif %}
- variables:
elapsed_time: >
{% set elapsed_timedelta = now() - as_datetime(start_time) %} {{
elapsed_timedelta.days * 86400 + elapsed_timedelta.seconds }}
elapsed_days: "{{ (elapsed_time | int) // 86400 }}"
elapsed_hours: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) * 86400)) //
3600 }}
elapsed_minutes: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) * 86400) -
((elapsed_hours|int) * 3600)) // 60 }}
elapsed_seconds: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) * 86400) -
((elapsed_hours|int) * 3600) - ((elapsed_minutes|int) * 60)) }}
event_type: start
energy_used: >
{% if energy_entity is not none %}
{{ (states(energy_entity) | float) - initial_energy }}
{% else %}
0
{% endif %}
# We do this next trick to make sure running_threshold_delay is greater than finish_threshold_delay, otherwise we'll never detect the finish event because the running event will always be triggered first
running_threshold_total_delay: "{{ running_threshold_delay.hours * 3600 + running_threshold_delay.minutes * 60 + running_threshold_delay.seconds }}"
finish_threshold_total_delay: "{{ finish_threshold_delay.hours * 3600 + finish_threshold_delay.minutes * 60 + finish_threshold_delay.seconds }}"
- variables:
running_threshold_total_delay: "{{ (finish_threshold_total_delay + 1) if (running_threshold_total_delay < finish_threshold_total_delay) else running_threshold_total_delay }}"
running_threshold_delay_hours: "{{ running_threshold_total_delay // 3600 }}"
running_threshold_delay_minutes: "{{ (running_threshold_total_delay - running_threshold_delay_hours * 3600) // 60 }}"
running_threshold_delay_seconds: "{{ (running_threshold_total_delay - running_threshold_delay_hours * 3600 - running_threshold_delay_minutes * 60) }}"
running_threshold_delay:
hours: "{{ running_threshold_delay_hours }}"
minutes: "{{ running_threshold_delay_minutes }}"
seconds: "{{ running_threshold_delay_seconds }}"
- choose:
- conditions: "{{ true }}"
sequence: !input "on_start"
- repeat:
until:
- condition: template
value_template: '{{ wait.trigger.id == "stop" }}'
sequence:
- wait_for_trigger:
- platform: numeric_state
entity_id: !input "power_entity"
for: "{{ finish_threshold_delay.hours }}:{{ finish_threshold_delay.minutes }}:{{ finish_threshold_delay.seconds }}"
below: !input "finish_threshold"
id: stop
- platform: state
entity_id:
- !input "power_entity"
id: running
for: "{{ running_threshold_delay.hours }}:{{ running_threshold_delay.minutes }}:{{ running_threshold_delay.seconds }}"
- variables:
event_type: "{{ wait.trigger.id }}"
energy_used: >
{% if energy_entity is not none %}
{{ (states(energy_entity) | float) - initial_energy }}
{% else %}
0
{% endif %}
- choose:
- conditions: "{{ event_type == 'running' }}"
sequence:
- variables:
elapsed_time: "{% set elapsed_timedelta = now() - as_datetime(start_time) %}{{ elapsed_timedelta.days * 86400 + elapsed_timedelta.seconds }}"
elapsed_days: "{{ (elapsed_time | int) // 86400 }}"
elapsed_hours: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400)) // 3600 }}
elapsed_minutes: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400) - ((elapsed_hours|int) * 3600)) // 60 }}
elapsed_seconds: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400) - ((elapsed_hours|int) * 3600) -
((elapsed_minutes|int) * 60)) }}
- choose:
conditions: "{{ true }}"
sequence: !input "on_running"
- conditions: "{{ event_type == 'stop' }}"
sequence:
- variables:
elapsed_time: >
{% set elapsed_timedelta = now() - as_datetime(start_time
+ finish_threshold_delay.hours * 3600 + finish_threshold_delay.minutes *
60 + finish_threshold_delay.seconds) %} {{ elapsed_timedelta.days
* 86400 + elapsed_timedelta.seconds }}
elapsed_days: "{{ (elapsed_time | int) // 86400 }}"
elapsed_hours: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400)) // 3600 }}
elapsed_minutes: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400) - ((elapsed_hours|int) * 3600)) // 60 }}
elapsed_seconds: >-
{{ ((elapsed_time | int) - ((elapsed_days|int) *
86400) - ((elapsed_hours|int) * 3600) -
((elapsed_minutes|int) * 60)) }}
- choose:
- conditions: "{{ true }}"
sequence: !input "on_stop"
mode: single
@jgillula
Copy link
Author

@babuckin
Copy link

I've tried for many hours to include the elapsed_time when the appliance stops in a notification, but failed. An example would be of great use to me.

@jgillula
Copy link
Author

I've tried for many hours to include the elapsed_time when the appliance stops in a notification, but failed. An example would be of great use to me.

Here's what I use, hope it helps:

{% set elapsed_time_string = (((24 * (elapsed_days|int) + (elapsed_hours|int)) | string) + " hours, " if ((elapsed_hours|int) > 0 or (elapsed_days|int) > 0) else "") + (elapsed_minutes|string) + " minutes" %}
The dryer ran for {{ elapsed_time_string }}.

If the number of days or hours is greater than zero, it combines them into a total number of hours, and then adds the minutes.

@babuckin
Copy link

With my code:

  • id: '1697054838140'
    alias: sumpAPM
    description: ''
    use_blueprint:
    path: jgillula/advanced_appliance_power_monitor.yaml
    input:
    power_entity: sensor.sump_pump_plug_current_consumption
    energy_entity: sensor.sump_pump_plug_today_s_consumption
    on_stop:
    - service: notify.mobile_app_bucks_iphone
    {% set elapsed_time_string = (((24 * (elapsed_days|int) + (elapsed_hours|int)) | string) + " hours, " if ((elapsed_hours|int) > 0 or (elapsed_days|int) > 0) else "") + (elapsed_minutes|string) + " minutes" %}
    data:
    message: The pump ran {{ elapsed_time_string }} seconds.

I get the error msg "UndefinedError: 'elapsed_hours' is undefined". I thought the path: statement would have enabled this reference.

@jgillula
Copy link
Author

Does it work if you move the Jinja template inside the message field, i.e.

message: >-
  {% set elapsed_time_string = (((24 * (elapsed_days|int) + (elapsed_hours|int)) | string) + " hours, " if ((elapsed_hours|int) > 0 or (elapsed_days|int) > 0) else "") + (elapsed_minutes|string) + " minutes" %}
  The pump ran {{ elapsed_time_string }} seconds.

(Also note that elapsed_time_string is going to be something like "27 hours, 34 minutes", not a number of seconds.)

@babuckin
Copy link

This updated code:

  • id: "1697054838140"
    alias: sumpAPM
    description: ""
    use_blueprint:
    path: jgillula/advanced_appliance_power_monitor.yaml
    input:
    power_entity: sensor.sump_pump_plug_current_consumption
    energy_entity: sensor.sump_pump_plug_today_s_consumption
    on_stop:
    - service: notify.mobile_app_bucks_iphone
    data:
    message: >-
    {% set elapsed_time_string = (((24 * (elapsed_days|int) + (elapsed_hours|int)) | string) + " hours, " if ((elapsed_hours|int) > 0 or (elapsed_days|int) > 0) else "") + (elapsed_minutes|string) + " minutes" %}
    The pump ran {{ elapsed_time_string }}

throws the same error "UndefinedError: 'elapsed_hours' is undefined" (I'm testing in Developer Tools->Template)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment