BACnet automation examples for Home Assistant
Complete automations for common tasks. Replace the entity IDs with your own. The examples use the automation syntax of Home Assistant 2024.10 and newer.
Occupancy override from a calendar
Command an area to occupied while a calendar event runs, and release the command when it ends so the controller's own schedule takes over again. Actions: bacnet.write_value and bacnet.relinquish. The occupancy object here is a Multi-State Output (a number entity); check its bacnet_state_text attribute for which value means occupied on your device.
alias: Meeting room occupied during calendar events
triggers:
- trigger: calendar
event: start
entity_id: calendar.meeting_room
id: start
- trigger: calendar
event: end
entity_id: calendar.meeting_room
id: end
actions:
- choose:
- conditions:
- condition: trigger
id: start
sequence:
- action: bacnet.write_value
target:
entity_id: number.meeting_room_occupancy
data:
value: 1 # occupied on this device
priority: 11
default:
- action: bacnet.relinquish
target:
entity_id: number.meeting_room_occupancy
data:
priority: 11 # release the same priority you wrote at
Always relinquish at the priority you wrote at. Turning a switch off or writing 0 commands the object off; only bacnet.relinquish releases it.
Temporary setpoint with a climate entity
Set a comfort setpoint in the morning and hand control back to the controller in the evening. Turning a BACnet climate entity off relinquishes its setpoint. See Climate.
alias: Office comfort setpoint on weekdays
triggers:
- trigger: time
at: "07:00:00"
id: "on"
- trigger: time
at: "18:00:00"
id: "off"
conditions:
- condition: time
weekday: [mon, tue, wed, thu, fri]
actions:
- if:
- condition: trigger
id: "on"
then:
- action: climate.set_temperature
target:
entity_id: climate.office_setpoint
data:
temperature: 21.5
else:
- action: climate.set_hvac_mode
target:
entity_id: climate.office_setpoint
data:
hvac_mode: "off"
Get notified when a device stops responding
Entities become unavailable when their device doesn't answer. After 10 failed polls a message also appears in Settings → Repairs; this automation notifies you sooner. See also Troubleshooting.
alias: BACnet device offline
triggers:
- trigger: state
entity_id: sensor.ahu_1_supply_temperature
to: unavailable
for: "00:05:00"
actions:
- action: notify.notify
data:
message: "AHU 1 is not responding to BACnet requests."
Notify on BACnet alarms
Uses the bacnet_alarm event; see Alarms.
alias: BACnet alarm notification
triggers:
- trigger: event
event_type: bacnet_alarm
actions:
- action: notify.notify
data:
message: >-
{% if trigger.event.data.event_state == 'normal' %}
Alarm cleared: {{ trigger.event.data.object_name }}
{% else %}
Alarm: {{ trigger.event.data.object_name }} is {{ trigger.event.data.event_state }}
{% endif %}
Calendar events as schedule exceptions
Instead of overriding the controller, add each calendar event to its Schedule object as an exception. The controller then knows about the event in advance, so features like optimal start can prepare the space. See Schedules for the rules. This automation adds the exception 12 hours before the event and removes it when the event ends.
alias: Calendar events as BACnet schedule exceptions
triggers:
- trigger: calendar
event: start
entity_id: calendar.meeting_room
offset: "-12:00:00"
id: add
- trigger: calendar
event: end
entity_id: calendar.meeting_room
id: remove
actions:
- action: bacnet.read_schedule
target:
entity_id: sensor.meeting_room_schedule
response_variable: schedule
- variables:
event_start: "{{ trigger.calendar_event.start }}"
event_end: "{{ trigger.calendar_event.end }}"
current: "{{ schedule['sensor.meeting_room_schedule'].exception_schedule or [] }}"
- if:
- condition: trigger
id: add
then:
- action: bacnet.write_schedule
target:
entity_id: sensor.meeting_room_schedule
data:
exception_schedule: >-
{% set start = as_local(as_datetime(event_start)) %}
{% set end = as_local(as_datetime(event_end)) %}
{{ current + [{
'period': {'date': start.date().isoformat()},
'priority': 11,
'time_values': [
{'time': start.strftime('%H:%M:%S'), 'value': 1},
{'time': end.strftime('%H:%M:%S'), 'value': none}
]
}] }}
else:
- action: bacnet.write_schedule
target:
entity_id: sensor.meeting_room_schedule
data:
exception_schedule: >-
{% set start = as_local(as_datetime(event_start)) %}
{% set ns = namespace(keep=[]) %}
{% for ex in current %}
{% set d = ex.period.get('date') %}
{% if not (d and ex.priority == 11 and d.year == start.year
and d.month == start.month and d.day == start.day) %}
{% set ns.keep = ns.keep + [ex] %}
{% endif %}
{% endfor %}
{{ ns.keep }}
allow_removal: true
value: 1is the occupied value and must match the schedule'svalue_type; runbacnet.read_scheduleonce in Developer tools → Actions to see what your schedule uses.noneat the end time hands control back to the weekly schedule.- The removal step drops every priority-11 exception on the event's date. Use a priority that nothing else on the controller uses for exceptions.
- Events that run past midnight need a
date_rangeperiod instead of a singledate.