-
-
Notifications
You must be signed in to change notification settings - Fork 503
Expand file tree
/
Copy pathbriefing.yaml
More file actions
executable file
·571 lines (516 loc) · 32 KB
/
Copy pathbriefing.yaml
File metadata and controls
executable file
·571 lines (516 loc) · 32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Speech Briefing Macros - TTS prompt helpers for daily briefings
# Related Issue: 1834
# Video: https://youtu.be/Gjk7z34Qe_4
# Blog: https://www.vcloudinfo.com/2026/07/home-assistant-air-quality-airly-pirate-weather.html
# Generates macro prompts for weather, reminders, and AI-driven speech routines.
# -------------------------------------------------------------------
# Weather, responsibilities, holidays, air quality, and fact prompts parsed by speech_processing/speech_engine.
# Notes: The nearest Airly CAQI is the primary local reading and stays silent
# below medium (50). Pirate Weather US AQI is a safety backstop at 101+.
# Notes: Person state `home` means someone is physically at Bear Stone.
# Greetings use only that dynamic in-home audience; off-site residents are
# reported separately by their current known location.
# Paige Home and Justin Dorm are spoken as home only for their respective child.
# Named personal zones such as Paige Work outrank reverse-geocoded venues.
# Prefer clean current place names; missing locations fall back to retained
# place name, neighborhood, city, then last named place.
# Notes: Previous broadcast text is stale context only; current sensor data
# stays authoritative for entry point and action wording.
# Notes: Weather context is included during the useful daytime window, when
# requested, or when safety thresholds make it relevant.
# Notes: Household context macros stay quiet unless current state adds action
# or abnormal status worth announcing.
######################################################################
>-
{%- macro dark_outside() -%}
[Because the sun has set, outside lights have been turned on]
{%- endmacro -%}
{%- macro garbage_day() -%}
{% set day_of_week = now().strftime('%a') %}
{% if day_of_week in ['Wed', 'Sun'] %}
Today is garbage day.
{% if day_of_week == 'Wed' %}
Both Recycling and regular Garbage goes out.
{% endif %}
{% endif %}
{%- endmacro -%}
{%- macro inside_weather() -%}
{% set temp = state_attr('climate.downstairs', 'current_temperature') | default('unknown') %}
{% set humidity = states('sensor.downstairs_thermostat_humidity') %}
Inside the house, it is {{ temp }} degrees{% if humidity not in ['unknown','unavailable','none'] %} with {{ humidity }} percent humidity{% endif %}. [Only mention humidity if it seems unusually high]
{%- endmacro -%}
{% macro outside_weather() %}
[Here is the current weather outside]
{% set airly_entity = 'sensor.bear_stone_common_air_quality_index' %}
{% set airly_index = states(airly_entity) | float(none) %}
{% set airly_level = state_attr(airly_entity, 'level') | default('', true) %}
{% set airly_description = state_attr(airly_entity, 'description') | default('', true) %}
{% set airly_alert = airly_index is not none and airly_index >= 50 %}
{% set pirate_aqi = states('sensor.pirateweather_air_quality_index') | float(none) %}
{% set pirate_alert = pirate_aqi is not none and pirate_aqi >= 101 %}
{% if airly_alert or pirate_alert %}
{% if pirate_alert %}
{% set pirate_value = pirate_aqi | round(0) | int %}
{% set pirate_category =
'unhealthy for sensitive groups' if pirate_value <= 150
else 'unhealthy' if pirate_value <= 200
else 'very unhealthy' if pirate_value <= 300
else 'hazardous' %}
{% endif %}
[Outdoor air quality alert:
{% if airly_alert %}Nearest Airly CAQI is {{ airly_index | round(0) | int }}{% if airly_level %}, rated {{ airly_level | lower }}{% endif %}.{% if airly_description %} {{ airly_description }}{% endif %}{% endif %}
{% if pirate_alert %}Pirate Weather US AQI is {{ pirate_value }}, which is {{ pirate_category }}.{% endif %}
Mention only the abnormal reading or readings and give brief, calm outdoor guidance appropriate to the severity.]
{% endif %}
{% set pirateweather_metrics = states.sensor
| selectattr('entity_id','search','pirateweather')
| rejectattr('state','in',['0','0.0','none','unknown','unavailable'])
| list %}
{% for entity in pirateweather_metrics %}
{% set value = entity.state %}
{% set unit = entity.attributes.unit_of_measurement | default('') %}
{% set base_name = entity.attributes.friendly_name | default(entity.entity_id) %}
{% set friendly_name = ' '.join(base_name.split(' ')[1:]) or base_name %}
{% set numeric = value | float(0) %}
{% set include = (
'Temperature' in friendly_name
or 'Minutely' in friendly_name
or 'Precip' in friendly_name
or ('Wind Speed' in friendly_name and numeric > 15)
or ('Cloud Coverage' in friendly_name and numeric > 75)
or ('Humidity' in friendly_name and (numeric < 50 or numeric > 85))
or ('Nearest Storm Distance' in friendly_name and numeric <= 10)
) %}
{% if include and 'UV Index' not in friendly_name %}
{{ friendly_name }}: {{ value }} {{ unit }}
{% endif %}
{%- endfor %}
{% set uv_index = states('sensor.pirateweather_uv_index') | float(0) %}
{% if uv_index >= 6 %}
UV index is {{ uv_index }}.
[Give a helpful suggestion based on the current UV index or weather conditions]
{% endif %}
{%- if states('sensor.nws_alerts') | int(0) > 0 -%}
{%- set alert_description = state_attr('sensor.nws_alerts', 'Alerts') | default('') %}
[Summarize the included weather alert and give overall details on any storms relevant to the residents of the home. Use the Situation Overview Section to best understand what is going on - Be sure to highlight any impacts to Seminole County or Tallahassee]
{{ alert_description }}
[END of Weather Alert]
{%- endif %}
{%- endmacro -%}
{%- macro lightning() -%}
{% set distance_km = states('sensor.blitzortung_lightning_distance') | float(none) %}
There have been {{ states('sensor.blitzortung_lightning_counter') }} lightning strikes detected{% if distance_km is not none %} within {{ (distance_km / 1.609344) | round(1) }} miles of our house{% else %} nearby{% endif %}.
{%- endmacro -%}
{%- macro fridge() -%}
{% set freezer = states('sensor.refrigerator_freezer_temp') %}
{% set fridge = states('sensor.refrigerator_fridge_temp') %}
{% set blink_temp = states('sensor.blink_blink1_temperature') %}
{% if blink_temp not in ['unknown', 'unavailable', 'none', ''] %}
The internal temperature of the refrigerator is currently {{ blink_temp }} degrees.
{% endif %}
{% if freezer not in ['unknown', 'unavailable', 'none', ''] and fridge not in ['unknown', 'unavailable', 'none', ''] %}
The freezer temperature is {{ freezer }} degrees and the fridge temperature is {{ fridge }} degrees.
{% endif %}
{% set door = states.binary_sensor.refrigerator_door_open %}
{% set door_open = states('binary_sensor.refrigerator_door_open') == 'on' and door is not none and (as_timestamp(now()) - as_timestamp(door.last_changed)) > 360 %}
{% if door_open %}The fridge door is currently open.{% endif %}
{%- endmacro -%}
{%- macro window_check() -%}
{% set open_entries = states.binary_sensor
| selectattr('state', 'eq', 'on')
| selectattr('attributes.device_class', 'eq', 'opening')
| map(attribute='attributes.friendly_name')
| list %}
{% set entry_count = open_entries | length %}
{% if entry_count > 0 -%}
[Current entry point state: {{ open_entries | join(', ') }} {{ 'is' if entry_count == 1 else 'are' }} still open and {{ 'needs' if entry_count == 1 else 'need' }} to be closed manually. Do not say any physical window or door was closed unless current sensor data says it is closed.]
{%- endif -%}
{%- endmacro -%}
{%- macro lock_check() -%}
{% if not is_state('group.locks', 'locked') -%}
The
{%- for state in states.lock -%}
{%- endfor %}
{% for group in states.lock|groupby('state') -%}
{%- for entity in group.list if entity.state == 'unlocked' -%}
{{ ' and' if loop.last and not loop.first }}
{{ entity.attributes.friendly_name }}
{%- endfor -%}
{%- endfor %}
needs to be locked.
{%- endif -%}
{%- endmacro -%}
{%- macro garage_check() -%}
{% if not is_state('group.garage_doors', 'closed') -%}
The
{%- for state in states.cover -%}
{%- endfor %}
{% for group in states.cover|groupby('state') -%}
{%- for entity in group.list if entity.state == 'open' and entity.attributes.device_class == 'garage' -%}
{{ ' and' if loop.last and not loop.first }}
{{ entity.attributes.friendly_name }}
{%- endfor -%}
{%- endfor %}
need to be closed.
{%- endif -%}
{%- endmacro -%}
{%- macro front_door_packages() -%}
{% if is_state('binary_sensor.front_door_packages_present', 'on') -%}
There appears to be a package waiting at the front door.
{%- endif -%}
{%- endmacro -%}
{%- macro medicine() -%}
{% if is_state('input_boolean.medicine', 'off') -%}
It looks like Carlo has not taken his medicine yet. Please make sure Carlo takes his medicine now.
{% endif -%}
{%- endmacro -%}
{%- macro tesla_charge_context() -%}
{% set battery = states('sensor.spaceship_battery_level') | float(none) %}
{% set snooze_until = as_timestamp(states('input_datetime.tesla_model_y_plug_in_snooze_until'), 0) %}
{% set last_garage_close = as_timestamp(states('input_datetime.tesla_model_y_last_garage_close'), 0) %}
{% set recent_arrival = last_garage_close > 0 and (as_timestamp(now()) - last_garage_close) < 43200 %}
{% if battery is not none
and battery < 50
and is_state('switch.spaceship_charge', 'off')
and is_state('person.carlo', 'home')
and is_state('person.stacey', 'home')
and as_timestamp(now()) >= snooze_until
and (recent_arrival or now().hour >= 17) -%}
Model Y battery is {{ battery | round(0) | int }} percent and it is not charging. Plug it in.
{%- endif -%}
{%- endmacro -%}
{%- macro energy_context() -%}
{% set powerwall_charge = states('sensor.powerwall_charge') | float(none) %}
{% set house_load = states('sensor.powerwall_load_power') | float(none) %}
{% if is_state('binary_sensor.powerwall_grid_status', 'off') -%}
Power is out{% if powerwall_charge is not none %}. Powerwall is at {{ powerwall_charge | round(0) | int }} percent{% endif %}. {% if house_load is not none and house_load > 5 %}House load is high; reduce large appliances.{% elif powerwall_charge is none or powerwall_charge < 75 %}Keep high-load devices off.{% else %}Stay aware of backup power until the grid returns.{% endif %}
{% elif powerwall_charge is not none and powerwall_charge < 50 -%}
Powerwall is at {{ powerwall_charge | round(0) | int }} percent while the grid is online. Keep backup charge in mind before running high-load devices.
{%- endif -%}
{%- endmacro -%}
{%- macro water_context() -%}
{% set valve_state = states('valve.phyn_shutoff_valve') %}
{% set hot_water = states('sensor.rheem_wh_available_hot_water') | float(none) %}
{% set compressor_health = states('sensor.rheem_wh_compressor_health') | float(none) %}
{% set tank_health = states('sensor.rheem_wh_tank_health') | float(none) %}
{% if valve_state == 'closed' and not is_state('binary_sensor.phyn_leak_test_running', 'on') -%}
Main water is shut off. Check for leaks before restoring water.
{% elif valve_state not in ['open', 'unknown', 'unavailable', 'none', ''] -%}
Main water valve is {{ valve_state }}. Check water status before using fixtures.
{%- endif %}
{% if hot_water is not none and hot_water < 30 -%}
Hot water is at {{ hot_water | round(0) | int }} percent. Keep showers short or wait for recovery.
{%- endif %}
{% if (compressor_health is not none and compressor_health < 90) or (tank_health is not none and tank_health < 90) -%}
Water heater health needs attention.
{%- endif -%}
{%- endmacro -%}
{%- macro comfort_context() -%}
{% set downstairs_current = state_attr('climate.downstairs', 'current_temperature') | float(none) %}
{% set downstairs_target = state_attr('climate.downstairs', 'temperature') | float(none) %}
{% set upstairs_current = state_attr('climate.upstairs', 'current_temperature') | float(none) %}
{% set upstairs_target = state_attr('climate.upstairs', 'temperature') | float(none) %}
{% set downstairs_humidity = states('sensor.downstairs_thermostat_humidity') | float(none) %}
{% if downstairs_humidity is none %}
{% set downstairs_humidity = state_attr('climate.downstairs', 'current_humidity') | float(none) %}
{% endif %}
{% if downstairs_current is not none and downstairs_target is not none and downstairs_current >= downstairs_target + 3 and is_state('binary_sensor.downstairs_ac_is_cooling', 'on') -%}
Downstairs is {{ downstairs_current | round(0) | int }} degrees and cooling toward {{ downstairs_target | round(0) | int }}.
{% elif downstairs_current is not none and downstairs_target is not none and downstairs_current >= downstairs_target + 4 and not is_state('climate.downstairs', 'off') -%}
Downstairs is {{ downstairs_current | round(0) | int }} degrees, above the {{ downstairs_target | round(0) | int }} degree target. Check the thermostat if it feels too warm.
{%- endif %}
{% if upstairs_current is not none and upstairs_target is not none and upstairs_current >= upstairs_target + 3 and is_state('binary_sensor.upstairs_ac_is_cooling', 'on') -%}
Upstairs is {{ upstairs_current | round(0) | int }} degrees and cooling toward {{ upstairs_target | round(0) | int }}.
{% elif upstairs_current is not none and upstairs_target is not none and upstairs_current >= upstairs_target + 4 and not is_state('climate.upstairs', 'off') -%}
Upstairs is {{ upstairs_current | round(0) | int }} degrees, above the {{ upstairs_target | round(0) | int }} degree target. Check the thermostat if it feels too warm.
{%- endif %}
{% if downstairs_humidity is not none and downstairs_humidity > 60 -%}
Downstairs humidity is {{ downstairs_humidity | round(0) | int }} percent. The house may feel warmer than the thermostat suggests.
{%- endif -%}
{%- endmacro -%}
{%- macro vacuum_context() -%}
{% set vac_state = states('vacuum.l10s_vacuum') %}
{% set vac_error = states('sensor.l10s_vacuum_error') %}
{% set room = states('sensor.l10s_vacuum_current_room') %}
{% set task_status = states('sensor.l10s_vacuum_task_status') %}
{% set phase = states('input_select.l10s_vacuum_phase') %}
{% set queue = states('input_text.l10s_vacuum_room_queue') | default('', true) | string | trim %}
{% set cleaned = states('input_text.l10s_vacuum_rooms_cleaned_today') | default('', true) | string | trim %}
{% set queue_count = queue | regex_findall('[0-9]+') | length %}
{% set valid_room = room not in ['unknown', 'unavailable', 'none', ''] %}
{% if vac_state == 'error' or vac_error not in ['no_error', 'unknown', 'unavailable', 'none', ''] -%}
Vacuum needs help{% if valid_room %} in {{ room }}{% endif %}. Error is {{ vac_error | replace('_', ' ') }}.
{% elif vac_state in ['cleaning', 'returning', 'paused'] -%}
Vacuum is {{ vac_state | replace('_', ' ') }}{% if valid_room %} near {{ room }}{% endif %}{% if queue_count > 0 %} with {{ queue_count }} rooms left{% elif task_status not in ['unknown', 'unavailable', 'none', ''] %}; task status is {{ task_status | replace('_', ' ') }}{% endif %}.
{% elif is_state('input_boolean.l10s_vacuum_on_demand', 'on') and queue_count > 0 -%}
Vacuum has {{ queue_count }} rooms queued for {{ phase | replace('_', ' ') }}.
{% elif cleaned not in ['unknown', 'unavailable', 'none', ''] -%}
Vacuum cleaned today: {{ cleaned }}.
{% endif %}
{%- endmacro -%}
{%- macro moon() -%}
{% if (now().hour == 17) %}
Current Moon phase: {{ states('sensor.moon') }} [Give a fact and mention today's phase]
{% endif %}
{%- endmacro -%}
{%- macro uv() -%}
{# UV details are now included in outside_weather with a threshold gate #}
{%- endmacro -%}
{%- macro holiday() -%}
{% if states('sensor.holiday') != '' %}
Today is {{ states('sensor.holiday') }}.
{% endif %}
{%- endmacro -%}
{# YOUTUBE VIDEO ********* https://www.vcloudinfo.com/2019/11/adding-days-until-sensor-to-my-home-assistant-speech-routines.html #}
{%- macro days_until() -%}
{%- if states('sensor.mothers_countdown') | int(9999)< 20 -%}
and don't forget, there is only {{ states('sensor.mothers_countdown') }} days until Mothers day!
{%- elif states('sensor.fathers_countdown') | int(9999)< 20 -%}
and don't forget, there are {{ states('sensor.fathers_countdown') }} days until Fathers day!
{%- elif states('sensor.easter_countdown') | int(9999)< 15 -%}
and don't forget, there are {{ states('sensor.easter_countdown') }} colorful days until Easter Sunday!
{%- elif states('sensor.thanksgiving_day_countdown') | int(9999)< 10 and states('sensor.thanksgiving_day_countdown') | int(9999)> 0 -%}
and don't forget, there are {{ states('sensor.thanksgiving_day_countdown') }} thankful days until Thanksgiving
{%- elif states('sensor.thanksgiving_day_countdown') | int(9999)< 1 -%}
and don't forget, Thanksgiving is tomorrow!
{%- elif states('sensor.halloween_countdown') | int(9999)< 30 and states('sensor.halloween_countdown') | int(9999)> 0 -%}
and don't forget, there are {{ states('sensor.halloween_countdown') }} Spooky days until Halloween!
{%- elif states('sensor.halloween_countdown') | int(9999)< 1 -%}
and don't forget, Tomorrow is Halloween!
{%- elif states('sensor.chanukkah_countdown') | int(9999)< 15 -%}
and don't forget, there are {{ states('sensor.chanukkah_countdown') }} days until Chanukkah!
{%- elif states('sensor.christmas_countdown') | int(9999)< 30 and states('sensor.christmas_countdown') | int(9999)> 0 -%}
and don't forget, there are {{ states('sensor.christmas_countdown') }} Merry days until Christmas!
{%- elif states('sensor.christmas_countdown') | int(9999)< 1 -%}
and don't forget, Santa Claus is coming tomorrow!
{% endif %}
{%- endmacro -%}
{# friendly duration/location helper #}
{% macro friendly_location(person_id, place_sensor_id, name) %}
{% set person = states[person_id] %}
{% if not person %}
{{ name }}: location has not updated
{% else %}
{% set person_state = states(person_id) %}
{% set unknown_locations = ['unknown', 'unavailable', '', 'none', 'not_home', 'not home', 'away'] %}
{% set place_state = states(place_sensor_id) %}
{% set place_known = place_state | lower not in unknown_locations %}
{% set person_known = person_state | lower not in unknown_locations %}
{% set person_key = person_state | lower %}
{% set named_zone_known = person_known and person_key not in ['home', 'driving'] %}
{% set zone_friendly_name = state_attr('zone.' ~ person_key, 'friendly_name') | default(person_key, true) | string | trim %}
{% set zone_words = zone_friendly_name | replace('_', ' ') | trim %}
{% set zone_words_key = zone_words | lower %}
{% set resident_prefix = (name | lower) ~ ' ' %}
{% set prefix_length = resident_prefix | length %}
{% set named_zone_label = zone_words[prefix_length:] if zone_words_key[:prefix_length] == resident_prefix else zone_words %}
{% set place_name = state_attr(place_sensor_id, 'place_name') | default('', true) | string | trim %}
{% set neighborhood = state_attr(place_sensor_id, 'place_neighbourhood') | default('', true) | string | trim %}
{% set city = state_attr(place_sensor_id, 'city') | default('', true) | string | trim %}
{% set last_place = state_attr(place_sensor_id, 'last_place_name') | default('', true) | string | trim %}
{% set place_name_known = place_name | lower not in unknown_locations %}
{% set neighborhood_known = neighborhood | lower not in unknown_locations %}
{% set city_known = city | lower not in unknown_locations %}
{% set last_place_known = last_place | lower not in unknown_locations %}
{% set current_place_label = place_name if place_name_known else place_state %}
{% set fallback_label = place_name if place_name_known else neighborhood if neighborhood_known else city if city_known else last_place if last_place_known else '' %}
{% set fallback_kind = 'place' if place_name_known else 'neighborhood' if neighborhood_known else 'city' if city_known else 'place' if last_place_known else '' %}
{% set using_fallback = not place_known and not person_known and fallback_label %}
{% set at_bear_stone = person_state == 'home' %}
{% set at_personal_home = (name == 'Paige' and person_key == 'paige_home') or (name == 'Justin' and person_key == 'justin_dorm') %}
{% set personal_home_label = 'Paige Home' if name == 'Paige' and person_key == 'paige_home' else 'Justin Dorm' if name == 'Justin' and person_key == 'justin_dorm' else '' %}
{% set location_label = personal_home_label if at_personal_home else named_zone_label if named_zone_known else current_place_label if place_known else fallback_label %}
{% set last_changed = as_timestamp(person.last_changed) %}
{% set seconds = (as_timestamp(now()) - last_changed) | int(0) if last_changed else 0 %}
{% set hours = (seconds // 3600) | int %}
{% set minutes = ((seconds % 3600) // 60) | int %}
{% set duration = (hours ~ ' hours') if hours >= 1 else (minutes ~ ' minutes') %}
{% if person_state == 'driving' %}
{% set driving_label = current_place_label if place_known else fallback_label %}
{{ name }}: driving{% if driving_label %} near {{ driving_label }}{% endif %}{% if seconds >= 60 %} for {{ duration }}{% endif %}
{% elif at_bear_stone %}
{{ name }}: at Bear Stone home{% if seconds >= 60 %} for {{ duration }}{% endif %}
{% elif at_personal_home %}
{{ name }}: home at {{ location_label }}{% if seconds >= 60 %} for {{ duration }}{% endif %}
{% elif using_fallback %}
{{ name }}: last known {{ 'in' if fallback_kind in ['neighborhood', 'city'] else 'near' }} {{ fallback_label }}
{% elif not location_label %}
{{ name }}: location has not updated{% if seconds >= 60 %} for {{ duration }}{% endif %}
{% else %}
{{ name }}: at {{ location_label }}{% if seconds >= 60 %} for {{ duration }}{% endif %}
{% endif %}
{% endif %}
{% endmacro %}
{# a macro that removes all newline characters, empty spaces, and returns formatted text and replaces underscores with spaces #}
{%- macro cleanup(data) -%}
{%- for item in data.split("\n") if item | trim != "" -%}
{{ item | trim | replace("_", " ") }} {% endfor -%}
{%- endmacro -%}
{# ********************************************* #}
{# ******** Start the Speech routines ******** #}
{# ********************************************* #}
{# a macro to call all macros :) #}
{%- macro mother_of_all_macros() -%}
{# Augmenting the System Prompt for OpenAI #}
{% set current_date = now() %}
{% set month = current_date.strftime('%B') %}
{% set day_of_week = now().strftime('%a') %}
{% set hour = now().hour %}
{% set minute = now().minute %}
{% set day = current_date.strftime('%d') %}
{% set year = current_date.strftime('%Y') %}
{% set time = current_date.strftime('%I:%M %p') %}
{% set call_no_announcement_flag = call_no_announcement | default(0) | int(0) %}
{% set call_inside_weather_flag = call_inside_weather | default(0) | int(0) %}
{% set call_outside_weather_flag = call_outside_weather | default(0) | int(0) %}
{% set call_dark_outside_flag = call_dark_outside | default(0) | int(0) %}
{% set call_garage_check_flag = call_garage_check | default(0) | int(0) %}
{% set call_window_check_flag = call_window_check | default(0) | int(0) %}
{% set call_garbage_day_flag = call_garbage_day | default(0) | int(0) %}
{% set no_fridge_flag = no_fridge | default(0) | int(0) %}
{% set sunset_ts = as_timestamp(state_attr('sun.sun', 'next_setting'), 0) %}
{% set daytime_weather_context = is_state('sun.sun', 'above_horizon') and sunset_ts > 0 and as_timestamp(now()) < (sunset_ts - 7200) %}
{% set lightning_close = states('sensor.blitzortung_lightning_counter') | int(0) > 0 and (states('sensor.blitzortung_lightning_distance') | float(9999) / 1.609344) <= 10 %}
{% set weather_safety_context = (
states('sensor.nws_alerts') | int(0) > 0
or states('sensor.pirateweather_wind_speed') | float(0) >= 30
or states('sensor.bear_stone_common_air_quality_index') | float(0) >= 50
or states('sensor.pirateweather_air_quality_index') | int(0) >= 101
or lightning_close
) %}
[Current date time: {{ month }} {{ day }}, {{ year }} {{ time }}]
{% set residents = [
('person.carlo', 'sensor.carlo_place', 'Carlo'),
('person.stacey', 'sensor.stacey_place', 'Stacey'),
('person.justin', 'sensor.justin_place', 'Justin'),
('person.paige', 'sensor.paige_place', 'Paige')
] %}
{% set at_bear_stone = namespace(names=[]) %}
{% for person_id, place_sensor_id, name in residents %}
{% if is_state(person_id, 'home') %}
{% set at_bear_stone.names = at_bear_stone.names + [name] %}
{% endif %}
{% endfor %}
[Presence and location rule:
- Bear Stone home means a resident's person state is exactly `home`.
- The announcement audience is the dynamic `People at Bear Stone` list below. Greet only those people, and never greet, welcome, or imply that any other resident is at Bear Stone.
- When a presence update is useful, report each off-site resident's known location to the people at Bear Stone. Those residents are status updates, not greeting recipients.
- Paige at Paige Home and Justin at Justin Dorm are each home at their own home. Do not describe either as away, and do not welcome them as being at Bear Stone.
- Prefer a resident's named personal zone over a nearby reverse-geocoded venue. For example, report Paige_Work as "Paige is at work."
- Other named places, including driving, are off-site locations.
- When a current location is unavailable, prefer the last-known named place, then neighborhood or city supplied below. Keep the words "last known" so stale data is not presented as current.
- Do not say "location unavailable." If no useful fallback exists, say the location has not updated.]
[LLM speech contract:
- Write a useful spoken household announcement, not a sensor report.
- Priority order: safety or action needed, useful implication, relevant context, then one optional human touch.
- Lead with the main thing that matters now. For urgent or narrow alerts, skip greetings and side notes.
- Translate sensor data into household meaning. Mention raw numbers only when they explain severity or change the action.
- Do not announce normal states just because they are available in context.
- Vary wording, sentence structure, openings, and closings so the house does not sound repetitive.
- Avoid robotic phrases like "currently", "detected", "please be advised", "sensor data", and "as an AI" unless needed for clarity.
- Add at most one small seasonal note, insight, or personality line, and only when no urgent action exists.
- Sound calm, practical, and natural. Do not joke during safety, security, water, appliance, or maintenance alerts.
]
[People at Bear Stone:
{% if at_bear_stone.names %}
- {{ at_bear_stone.names | join(', ') }}
{% else %}
- Nobody is currently at Bear Stone.
{% endif %}
]
[Off-site resident locations:
{% for person_id, place_sensor_id, name in residents if not is_state(person_id, 'home') %}
- {{ friendly_location(person_id, place_sensor_id, name) }}
{% else %}
- Everyone is currently at Bear Stone.
{% endfor %}
]
[Household context:
{% if range(1, 100) | random <= 25 %}
Cat Molly: Always home.
{% endif %}
]
[Primary event/requested action:
{{ personarriving | default('', true) }}
{{ NestStatus | default('', true) }}
{{ DoorOpened | default('', true) }}
{{ DoorClosed | default('', true) }}
{{ DoorLocked | default('', true) }}
{{ DoorUnLocked | default('', true) }}
{{ NewDevice | default('', true) }}
{% set primary_value = value1 | default('', true) | trim %}
{% if primary_value %}
{{ primary_value }}
{% endif %}
]
[Optional greeting seed:
{% if call_no_announcement_flag != 1 %}
{% if now().strftime('%H')|int(9999)< 12 and now().strftime('%H')|int(9999)> 6 %}
Good morning. [if there is only one person home, address them specifically]
{% elif now().strftime('%H')|int(9999)>= 12 and now().strftime('%H')|int(9999)< 17 %}
Good afternoon. [if there is only one person home, address them specifically]
{% else %}
Good evening. [if there is only one person home, address them specifically]
{% endif %}
{% endif %}
Use this only if it improves flow. Address only the `People at Bear Stone` list above; if that list is empty, do not greet anyone. Skip it for repeated, urgent, narrow, or very short announcements.
]
[Context data:
{% if call_inside_weather_flag == 1 %}
{{ inside_weather() }}
{% endif %}
{% if call_outside_weather_flag == 1 or daytime_weather_context or weather_safety_context %}
{{ outside_weather() }}
{% endif %}
{% if (states('sensor.blitzortung_lightning_counter')|int(0)) > 0 and (call_outside_weather_flag == 1 or lightning_close) %}
{{ lightning() }}
{% endif %}
{% set blink_temp = states('sensor.blink_blink1_temperature') | float(none) %}
{% set freezer_temp = states('sensor.refrigerator_freezer_temp') | float(none) %}
{% set fridge_temp = states('sensor.refrigerator_fridge_temp') | float(none) %}
{% set fridge_door = states.binary_sensor.refrigerator_door_open %}
{% set fridge_door_open = states('binary_sensor.refrigerator_door_open') == 'on' and fridge_door is not none and (as_timestamp(now()) - as_timestamp(fridge_door.last_changed)) > 360 %}
{% if ((blink_temp is not none and blink_temp > 55) or (freezer_temp is not none and freezer_temp > 5) or (fridge_temp is not none and fridge_temp > 50) or fridge_door_open) and no_fridge_flag != 1 %}
{{ fridge() }}
{% endif %}
{{ lock_check() }}
{% if call_dark_outside_flag == 1 %}
{{ dark_outside() }}
{% endif %}
{% if call_garage_check_flag == 1 or is_state('sun.sun', 'below_horizon') %}
{{ garage_check() }}
{% endif %}
{% if (call_window_check_flag == 1 or is_state('sun.sun', 'below_horizon')) or is_state('group.entry_points', 'on') %}
{{ window_check() }}
{% endif %}
{{ front_door_packages() }}
{{ tesla_charge_context() }}
{{ energy_context() }}
{{ water_context() }}
{{ comfort_context() }}
{% if call_garbage_day_flag == 1 %}
{{ garbage_day() }}
{% endif %}
{% if now().strftime('%H')|int(0) > 21 %}
{{ medicine() }}
{% endif %}
{{ vacuum_context() }}
{{ holiday() }}
{# call a Random fact about the house or inspiration quote #}
{{ ([moon, holiday, days_until ]|random)() }}
]
[Previous broadcast rule:
The previous broadcast is stale context for repetition avoidance only.
Do not quote it, continue it, summarize it, trust it as current state, or repeat its exact phrasing.
If the same issue is still active, restate it from current Sensor Data with fresh wording.
Do not repeat prior claims that a window, door, lock, garage door, light, appliance, or weather condition changed unless current Sensor Data supports that claim.
If current Sensor Data says an entry point is still open and needs closure, say it is still open or needs attention, not that it is closed.
]
[Previous broadcast for context: "{{ state_attr('sensor.openai_response', 'response') }}" ]
{%- endmacro -%}
{{- cleanup(mother_of_all_macros()) -}}