How do you define a custom attribute template with an automation trigger? Can you provide an example of what you mean?
I define it with a default value in my configuration.yaml and I use automation triggers to set the values.
That statement is unclear to me because triggers don’t set values. Services set values and services are called within a script or in an automation’s action. Can you clarify what you mean exactly? Perhaps with an example?
The value of a Template Sensor’s attribute can be set by the Template Sensor’s attribute_templates
.
So I think I got it working.
I don’t like the code and the fact that I can’t reuse my set variables throughout the template, I wish we had a way to use scope variables when using templates…
This is how I set the binary_sensor with a list of batteries that I need to replace:
binary_sensor:
- platform: template
sensors:
battery_devices_issues:
friendly_name: 'Battery Devices Issues'
device_class: problem
value_template: >-
{% set vars = namespace(
numberOfBatteryDevices = 0,
numberOfEmptyBatteryDevices = 0,
emptyBatteryDevices = '',
numberOfNonResponsiveDevices = 0,
nonResponsiveDevices = '',
numberOfLowBatteryDevices = 0,
lowBatteryDevices = '',
goodBatteryDevices = ''
) -%}
{%- for curr_entity in (states |selectattr('entity_id')| list) -%}
{%- if curr_entity.entity_id.endswith('_battery_level') -%}
{%- set _batteryDevice=curr_entity.entity_id -%}
{%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
{%- set _batteryLevel = states[_batteryDevice].state -%}
{%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature' -%}
{%- set _lastChanged = states[_temperatureSensor].last_changed -%}
{%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
{%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
{%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600 %}
{%- if _batteryLevel=='unavailable' -%}
{%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
{%- elif _hoursPassed>=2 -%}
{%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
{%- elif _batteryLevel|int <40 -%}
{%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
{%- else -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
{%- endif -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{%- set _totalBatteryIssues = vars.numberOfEmptyBatteryDevices + vars.numberOfNonResponsiveDevices %}
{{ _totalBatteryIssues>0 }}
attribute_templates:
empty_batteries: >-
{% set vars = namespace(
numberOfBatteryDevices = 0,
numberOfEmptyBatteryDevices = 0,
emptyBatteryDevices = '',
numberOfNonResponsiveDevices = 0,
nonResponsiveDevices = '',
numberOfLowBatteryDevices = 0,
lowBatteryDevices = '',
goodBatteryDevices = ''
) -%}
{%- for curr_entity in (states |selectattr('entity_id')| list) -%}
{%- if curr_entity.entity_id.endswith('_battery_level') -%}
{%- set _batteryDevice=curr_entity.entity_id -%}
{%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
{%- set _batteryLevel = states[_batteryDevice].state -%}
{%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature' -%}
{%- set _lastChanged = states[_temperatureSensor].last_changed -%}
{%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
{%- set vars.numberOfBatteryDevices = vars.numberOfBatteryDevices+ 1-%}
{%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600 %}
{%- if _batteryLevel=='unavailable' -%}
{%- set vars.numberOfEmptyBatteryDevices = vars.numberOfEmptyBatteryDevices+ 1 -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~',' if vars.emptyBatteryDevices!='' -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~_sensorName -%}
{%- elif _hoursPassed>=2 -%}
{%- set vars.numberOfNonResponsiveDevices = vars.numberOfNonResponsiveDevices+ 1 -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~',' if vars.nonResponsiveDevices!='' -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~_sensorName -%}
{%- elif _batteryLevel|int <40 -%}
{%- set vars.numberOfLowBatteryDevices = vars.numberOfLowBatteryDevices+ 1 -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~',' if vars.lowBatteryDevices!='' -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~_sensorName -%}
{%- else -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~',' if vars.goodBatteryDevices!='' -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~_sensorName -%}
{%- endif -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{{vars.emptyBatteryDevices ~','~ vars.nonResponsiveDevices }}
- I still need to work on this, the code doesn’t meet my coding standards but it is working.
@123 Thanks for the lead with custom attributes.
Hi buddy,
I am sorry if I confused you.
I figured out how to get the binary_sensor to work but to answer your question:
defining a custom attribute in configuration.yaml for all the entities that are tracked by my alarm logic
customize_glob:
"light.*shade":
icon: mdi:window-shutter
"light.*":
alarm_tracking: true
"binary_sensor.*_motion_sensor":
alarm_tracking: true
"binary_sensor.*_contact_sensor":
alarm_tracking: true
"binary_sensor.*_door_sensor":
alarm_tracking: true
"lock.*":
alarm_tracking: true
customize:
binary_sensor.vault_sensor:
alarm_tracking: true
switch.outside_fan:
alarm_tracking: true
example for using the custom attribute in an automation:
- id: '1111111111111'
alias: Security - Alarm Armed Away
description: ''
trigger:
- platform: device
device_id: c2a59ce4973c2a59ce4973c2a59ce4973
domain: alarm_control_panel
entity_id: alarm_control_panel.security_mode
type: armed_away
condition: []
action:
- service: notify.all_devices
data:
title: 🔔Alarm Was Armed- Securing Home!
message: You will recieve another notification when the shut down completes.
data:
subtitle: Home is shutting down
url: /home-security/security_panel
push:
sound:
name: default
critical: 1
volume: 1
thread-id: presence-notification-group
category: alarm_armed
action_data:
- identifier: AUTHORIZE_PRESENCE
title: Authorized person is home
apns_headers:
apns-collapse-id: alarm-armed-notification
presentation_options:
- alert
- service: script.home_off
data: {}
- delay:
hours: 0
minutes: 1
seconds: 0
milliseconds: 0
- service: notify.all_devices
data:
title: Alarm Was Armed- Home Secured!
message: "{%- set tracked_devices = states | selectattr('attributes.alarm_tracking',\
\ 'defined')|selectattr('state','in', ['on', 'unlocked', 'unavailable'])|\
\ list -%} {%- for device in tracked_devices -%}\n {{device.name ~ \" is\
\ \" ~ device.state ~ \",\\n\" }}\n{%- endfor -%}"
data:
subtitle: '{%- set counter= states | selectattr(''attributes.alarm_tracking'',
''defined'')|selectattr(''state'',''in'', [''on'', ''unlocked'', ''unavailable''])|
list| count -%} {%- if counter>0 -%} {{ ''Pay attention to '' ~ counter
~ '' security incidents:''}} {% else %} {{ ''No Security incidents''}} {%
endif %}'
url: /home-security/security_panel
push:
sound:
name: default
critical: 1
volume: 1
thread-id: presence-notification-group
category: alarm_armed
action_data:
- identifier: AUTHORIZE_PRESENCE
title: Authorized person is home
apns_headers:
apns-collapse-id: alarm-armed-notification
presentation_options:
- alert
Your example of ’ using the custom attribute in an automation’ does not show what you claimed earlier:
I define it with a default value in my configuration.yaml and I use automation triggers to set the values
I see where you defined it but I don’t see where in that automation it sets the value of a custom attribute; it reads the value of a custom attribute.
As for the binary_sensor example, it demonstrate the point I made earlier regarding the advantage of using custom attributes to produce compact templates. The binary_sensor relies on a lengthy template (due to the need to parse metadata out of an entity’s name) and then is obligated to duplicate the entire lengthy template in attributes_template
.
FWIW, the following template casts a very wide net because it uses the State Objects of all entities in your system:
states | selectattr('entity_id') | list
If you know that only certain domains have battery-operated devices then you can narrow the field to include only those domains:
expand(states.light, states.sensor, states.binary_sensor, states.lock) | selectattr('entity_id') | list
For future reference, here’s an alternate way of reporting entities with low battery levels. The user had the additional need to provide each battery-operated device with its own low-battery threshold (as opposed to using the same threshold for all entities). If you only require a single threshold value then it can be specified with customize_glob (as opposed to a per entity basis) given that your entities adhere to a naming convention.
Thanks buddy, You gave me some useful tips here.
I will definitely implement them.
The link you attached would not work for my needs since I don’t want a time triggered notification Nor adding state triggers for each sensor.
My example is using a custom template, once it works, it will report any battery sensor as soon as it becomes unresponsive or unavailable or crosses the low-battery threshold (40%)
It works just fine now but I will be implementing your suggestion to narrow the for-loop scope to the desired domain.
You have been most helpful, not just here, I read a lot of your posts here and they are gold!
@123
I finally found a solution that I am comfortable with.
this is how I set my binary_sensor, without repeating templates and with a bit of clever coding:
binary_sensor:
- platform: template
sensors:
battery_devices_issues:
friendly_name: 'Battery Devices Issues'
device_class: problem
value_template: >-
{% set batteryDevicesStatus = (state_attr('binary_sensor.battery_devices_issues', 'all_battery_devices_status')) %}
{{batteryDevicesStatus.empty_battery_devices|count+batteryDevicesStatus.non_responsive_devices|count>0}}
attribute_templates:
all_battery_devices_status: >-
{% set vars = namespace(
emptyBatteryDevices = '',
nonResponsiveDevices = '',
lowBatteryDevices = '',
goodBatteryDevices = ''
) -%}
{%- for curr_entity in (expand(states.sensor) | selectattr('entity_id') | list) -%}
{%- if curr_entity.entity_id.endswith('_battery_level') -%}
{%- set _batteryDevice=curr_entity.entity_id -%}
{%- set _sensorName= curr_entity.entity_id.split('.')[1].split('_battery_level')[0] -%}
{%- set _batteryLevel = states[_batteryDevice].state -%}
{%- set _temperatureSensor = 'sensor.'~_sensorName~'_temperature' -%}
{%- set _lastChanged = states[_temperatureSensor].last_changed -%}
{%- if _lastChanged!=null -%} {#probably a phone, not a sensor!#}
{%- set _hoursPassed = (((as_timestamp(now()) - as_timestamp(_lastChanged)) | int) % 86400)//3600 %}
{%- if _batteryLevel=='unavailable' -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~"," if vars.emptyBatteryDevices!="" -%}
{%- set vars.emptyBatteryDevices = vars.emptyBatteryDevices~ "'" ~ _sensorName ~ "'" -%}
{%- elif _hoursPassed>=2 -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~"," if vars.nonResponsiveDevices!="" -%}
{%- set vars.nonResponsiveDevices = vars.nonResponsiveDevices~ "'" ~ _sensorName ~ "'" -%}
{%- elif _batteryLevel|int <40 -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~"," if vars.lowBatteryDevices!="" -%}
{%- set vars.lowBatteryDevices = vars.lowBatteryDevices~ "'" ~ _sensorName ~ "'"-%}
{%- else -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~"," if vars.goodBatteryDevices!="" -%}
{%- set vars.goodBatteryDevices = vars.goodBatteryDevices~ "'" ~ _sensorName ~ "'" -%}
{%- endif -%}
{%- endif -%}
{%- endif %}
{%- endfor -%}
{%- set batteryDevicesStatusJSON = "{'empty_battery_devices':["~vars.emptyBatteryDevices~"],
'non_responsive_devices':["~vars.nonResponsiveDevices~"],
'low_battery_devices':["~vars.lowBatteryDevices~"],
'good_battery_devices':["~vars.goodBatteryDevices~"],
}" -%}
{{batteryDevicesStatusJSON}}
I am setting a JSON in a custom attribute that I can access from anywhere in home assistant + the sensor accesses it’s own custom attribute to determine it’s state.
Now I am getting notifications as I wanted, no need to update any scripts when I add sensors.
If you’re interested, I refactored the template using several techniques that help to reduce its length and complexity.
I have created several battery_level and temperature sensors (as Template Sensors) in order to test the streamlined version of the template. It appears to work as desired but the acid-test will, of course, be your system’s sensors.
battery_devices_issues:
friendly_name: 'Battery Devices Issues'
device_class: problem
value_template: >-
{% set status = (state_attr('binary_sensor.battery_devices_issues', 'status')) %}
{{ status.empty|count + status.dead|count > 0 }}
attribute_templates:
status: >-
{% set ns = namespace(empty = [], dead = [], low = [], good = []) %}
{% for entity in expand(states.sensor) if entity.entity_id.endswith('_battery_level') %}
{% set name = entity.object_id[:-14] %}
{% set level = states(entity.entity_id) %}
{% set changed = states['sensor.'~name~'_temperature'].last_changed %}
{% if changed != null %}
{% if level == 'unavailable' %} {% set ns.empty = ns.empty + [name] %}
{% elif now() - changed >= timedelta(hours=2) %} {% set ns.dead = ns.dead + [name] %}
{% elif level|int < 40 %} {% set ns.low = ns.low + [name] %}
{% else %} {% set ns.good = ns.good + [name] %}
{% endif %}
{% endif %}
{% endfor %}
{{ "{{\"empty\": {}, \"dead\": {}, \"low\": {}, \"good\": {} }}".format(ns.empty, ns.dead, ns.low, ns.good) }}
What has changed
Although I changed the names of many variables, that’s purely cosmetic (feel free to replace them with your original names ). The important changes are:
- The type of the namespace variables is list instead of string. This alone eliminates a lot of “list-building” code.
- The for-loop incorporates the initial if-statement in order to reduce iterations.
- Parsing the sensor’s root-name is simplified by referencing the entity’s
object_id
and slicing off the last 14 characters. - Eliminating variables for expressions that are only used once.
- Simplifying the time calculation by using native datetime and timedelta objects.
- Generate the final JSON string using the
format
method.
Awesome!
I started coding for home assistant a couple of weeks ago, I am still learning the framework (and jinja).
The HA documentation is lacking, especially in the more complex scenarios, I appreciate your help.
I will test it and report back.
[UPDATE]
Works as expected!
Glad to hear it passed the test.
I made one more simplification. This line contains unnecessary filtering:
{% for entity in (expand(states.sensor)|selectattr('entity_id')|list) if entity.entity_id.endswith('_battery_level') %}
and is reduced to this:
{% for entity in expand(states.sensor) if entity.entity_id.endswith('_battery_level') %}
You should know that the use of states.sensor
means this template will be evaluated every time any of your sensors changes state. If battery-powered devices comprise the majority of your sensors then using states.sensor
is fine. If they represent the minority then states.sensor
casts much too wide a net.
The simplest technique to improve efficiency is to create a group containing all battery-powered entities. The for-loop statement is reduced to:
{% for entity in expand('group.battery_powered') %}
and the template is evaluated only when one of the group’s members changes state (as opposed to any and all sensors in your system).
If manually creating the group seems like an onerous task then one could use an automation, triggered at startup, to automatically create the group and populate it with sensors whose names end with the phrase “battery_level”. The only potential ‘gotcha’ with this technique is if, on startup, Home Assistant creates the Template Binary Sensor before the automation creates the group. The sequence of what gets created on startup becomes important here.
Well, about 90% of my sensors are battery powered, I advocate for efficiency most of the time, but at a point where efficiency costs convenience or risks bugs, it simply doesn’t worth it.
I am running Home assistant on a VM on a strong machine, If needed, I can throw more resources at it but right now, it doesn’t go passed 5% CPU so I can probably run it on a much weaker machine.
I am coding a smart-home that works, with minimal intervention of my behalf. I had 4 different smart-home platforms over the passed 17 years (I started with X10). reporting devices and sensors health is a huge part of a functioning system. it deserves the resources and not having to update scripts when adding new sensors is important for future-proofing your smart home setup.
As I explained, it’s entirely possible to create a self-updating group that permits the Template Binary Sensor to monitors only battery-powered sensors; it works with minimal intervention on your behalf. Aside from improved efficiency, it offers the versatility and convenience of a group (of all battery-powered sensors) that can be referenced by other Template Sensors, automations, etc.
The concept of self-updating groups gained more interest when, several versions ago, Home Assistant lost default groups like All Lights, All Switches, etc. The belief was that many users didn’t need the default groups so the development team felt they introduced needless overhead. For the users who did use the default groups, the technique I described manages them without the bother of manual maintenance.
FWIW, my first foray into home automation employed X10 ActiveHome software which I replaced with MisterHouse for awhile. I also experimented with HouseBot (and a few others) but ultimately chose Premise back in 2007. I have been using it ever since; Premise and Home Assistant work together to automate my home. My so-called legacy devices, for which no integrations exist in Home Assistant, continue to be managed by Premise and exposed to Home Assistant via MQTT.
Glad to be speaking to a fellow smart-home veteran.
My current set up is a Lutron Homeworks system that I installed 15 years ago, it is hard-wired to every outlet and every light at my house, it still works and the web interface I wrote for it works as a great redundancy when everything else fails (as more complex systems sometimes do).
I wrote Telnet-MQTT-WebAPI bridge for it and brought it with me to my next set ups: alexa, apple homekit, smartthings and now home assistant.
The rest of my smart network is a mesh of zigbee sensors, broadlink remotes, some raspberries and arduino based projects that controls locks, irrigation and a bunch of APIs for every device at my home that supports any kind of connectivity.
My latest and proudest is a smart grill I built with a blow fan and sensors to control the fire temperature.
I am migrating all of the above to home assistant because I want local execution, for security, future proofing, internet connection independence, and faster response times.
How long have you been working with home assistant? You seem to know your way around the framework and templating.
I just confirmed that the proposed group-based technique works fine on startup.
The following automation creates group.battery_powered
and populates it with all sensors whose names end with “_battery_level”. It does this on startup and whenever Reload Groups is executed (more triggers can be added).
- alias: 'Create Groups'
trigger:
- platform: homeassistant
event: start
- platform: event
event_type: 'call_service'
event_data:
domain: 'group'
service: 'reload'
action:
- service: group.set
data:
object_id: battery_powered
name: 'Battery Powered'
entities: >
{% set ns = namespace(sensors = []) %}
{% for entity in states.sensor if entity.entity_id.endswith('_battery_level') %}
{% set ns.sensors = ns.sensors + [entity.entity_id] %}
{% endfor %}
{{ ns.sensors }}
The Template Binary Sensor now monitors fewer sensors. The for-loop statement is reduced to:
{% for entity in expand('group.battery_powered') %}
Like so:
battery_devices_issues:
friendly_name: 'Battery Devices Issues'
device_class: problem
value_template: >-
{% set status = (state_attr('binary_sensor.battery_devices_issues', 'status')) %}
{{ status.empty|count + status.dead|count > 0 }}
attribute_templates:
status: >-
{% set ns = namespace(empty = [], dead = [], low = [], good = []) %}
{% for entity in expand('group.battery_powered') %}
{% set name = entity.object_id[:-14] %}
{% set level = states(entity.entity_id) %}
{% set changed = states['sensor.'~name~'_temperature'].last_changed %}
{% if changed != null %}
{% if level == 'unavailable' %} {% set ns.empty = ns.empty + [name] %}
{% elif now() - changed >= timedelta(hours=2) %} {% set ns.dead = ns.dead + [name] %}
{% elif level|int < 40 %} {% set ns.low = ns.low + [name] %}
{% else %} {% set ns.good = ns.good + [name] %}
{% endif %}
{% endif %}
{% endfor %}
{{ "{{\"empty\": {}, \"dead\": {}, \"low\": {}, \"good\": {} }}".format(ns.empty, ns.dead, ns.low, ns.good) }}
I joined this community forum in October 2018. At the time I was experimenting with openHAB (for several months). I decided I preferred something that behaved more like Premise so I began exploring Home Assistant. As I slowly became comfortable with it, I took my time to carefully transfer/convert what I had created in Premise to Home Assistant. I waited until Home Assistant’s UPB integration was finished before transferring control of all lighting. I don’t recall when Home Assistant was no longer in “test” but in “production” in my home but it’s between 1 and 2 years now.
In Premise, I developed several integrations notably for the ELK M1 security panel, Environment Canada weather service, HAI Omnistat2 RC-2000 thermostat, etc. Most of my work is posted on the Cocoontech.com site (best home automation community back in the day; now just a shadow of its former self). The last one I created (early 2018) was an MQTT integration to allow Premise to communicate with other systems. That’s when I started dabbling with openHAB followed by Home Assistant in late 2018.
What I haven’t mentioned is that development of Premise was halted in 2006 and the product became freely available to the public (original price was over US$1000). I jumped on board in 2007. It’s a testament to its advanced architecture that users were able to continue enhancing it even though it remained closed-source.
Anyway, all ancient history now.
I rather think of it as evolution. Some of the dinosaur code I wrote is still running.
BTW:
Just a suggestion, I won’t be implementing groups, but if I had, I would have used your automatic group technique to create groups for each battery state “dead”, “good”, “low” & “empty”.
That’s inadvisable because the group’s membership is defined exclusively at the moment the group is created. If a sensor’s battery status changes afterwards, it won’t be reflected in the group’s membership. A Template Sensor is better suited for the task because it listens for state-changes (for all entities detected in its template).
I suppose one could use an automation to monitor all battery-powered sensors then update the groups whenever a sensor changes state. However, the automation’s trigger would have to explicitly list each sensor and that fails to meet the objective of avoiding manual maintenance (i.e. if you add a new battery-powered sensor you’ll have to manually update the automation’s trigger).
Are you sure it can’t be done with a template trigger somehow?