alias: TTS Status Notification
description: >
This script provides a comprehensive spoken summary of important information
at any time of day—current time, appointments, birthdays, holidays, sleep
quality, weather conditions, battery checks, health metrics, tasks, and more.
It integrates with multiple Home Assistant sensors, calendars, and a to-do
entity. It then refines the message via ChatGPT to improve grammar, style, and
context-based reminders.
Prior to running this, you need to place the following code in your
configuration.yml:
Due to max character limits, its provided in the next post. But here’s the drop-in code for the script you can paste into a script made with the web UI:
mode: restart
sequence:
- action: weather.get_forecasts
data:
type: hourly
target:
entity_id: weather.forecast_home
response_variable: weather_forecast_hourly
- action: calendar.get_events
data:
duration:
hours: 24
minutes: 0
seconds: 0
target:
entity_id:
- calendar.facebook_birthdays
response_variable: birthday_calendars
- action: calendar.get_events
data:
duration:
hours: 24
minutes: 0
seconds: 0
target:
entity_id:
- calendar.christian_holidays
- calendar.hindu_holidays
- calendar.holidays_in_united_states
- calendar.jewish_holidays
- calendar.muslim_holidays
response_variable: holiday_calendars
- action: calendar.get_events
data:
duration:
hours: 24
minutes: 0
seconds: 0
target:
entity_id:
- calendar.blzalewski_gmail_com
response_variable: appointment_calendars
- choose:
- conditions:
- condition: template
value_template: >-
{{ states('sensor.morris') is not none and
states('sensor.morris')|int >= 1 }}
sequence:
- action: google_generative_ai_conversation.generate_content
data:
prompt: >
The weather alert sensor shows the following JSON {{
state_attr('sensor.martin', 'alerts') }}. Please provide an
extremely short, concise summary of the weather alerts in simple
terms.
response_variable: chatgpt_alert_summary
- variables:
features: |
{% set f = {
'include_greeting_and_time': True,
'include_appointments': True,
'include_holidays': True,
'include_birthdays': True,
'include_sleep_score': True,
'include_battery_checks': True,
'include_weather_alerts': True,
'include_weather': True,
'include_zodiac': True,
'include_full_moon': True,
'include_sunday_reminder': True,
'include_motivational_quotes': True,
'include_tasks': True,
'include_smart_predictions': True,
'include_air_quality': True,
'night_mode': now().hour|int >= 21 or now().hour <= 4
} %} {{ f }}
- variables:
current_hour: "{{ now().hour }}"
current_day_str: "{{ now().strftime('%A, %B %-d, %Y') }}"
current_time_str: "{{ now().strftime('%-I:%M %p') }}"
weekday: "{{ now().strftime('%A') }}"
is_weekend: "{{ now().strftime('%A') in ['Saturday','Sunday'] }}"
today_str: "{{ now().strftime('%Y-%m-%d') }}"
tomorrow_str: "{{ (now() + timedelta(days=1)).strftime('%Y-%m-%d') }}"
day_after_tomorrow_str: "{{ (now() + timedelta(days=2)).strftime('%Y-%m-%d') }}"
sleep_score_sensor: "{{ states('sensor.withings_sleep_score') }}"
phone_battery_sensor: "{{ states('sensor.pixel_8_pro_battery_level') }}"
watch_battery_sensor: "{{ states('sensor.google_pixel_watch_3_battery_level') }}"
morris_sensor: "{{ states('sensor.morris') }}"
moon_phase_sensor: "{{ states('sensor.moon_phase') }}"
zodiac_today_sensor: "{{ states('sensor.zodiac')|default('unknown') }}"
zodiac_yesterday_sensor: "{{ states('sensor.yesterdays_zodiac')|default('unknown') }}"
local_air_quality_sensor: "{{ states('sensor.local_outdoor_air_quality') }}"
today_steps: "{{ states('sensor.brian_z_steps')|int }}"
avg_steps: "{{ states('sensor.weekly_average_steps') | int(default=0) }}"
today_hr: "{{ states('sensor.heart_rate')|int }}"
avg_hr: "{{ states('sensor.weekly_average_heart_rate')|int }}"
today_resting_hr: "{{ states('sensor.resting_heart_rate')|int }}"
avg_resting_hr: "{{ states('sensor.weekly_average_resting_heart_rate')|int }}"
today_oxygen: "{{ states('sensor.oxygen_saturation')|float }}"
avg_oxygen: "{{ states('sensor.weekly_average_oxygen_saturation')|float }}"
today_bglucose: "{{ states('sensor.blood_glucose')|float(default=0) }}"
avg_bglucose: "{{ states('sensor.weekly_average_blood_glucose')|float(default=0) }}"
today_heart_points: "{{ states('sensor.heart_points_daily')|int }}"
avg_heart_points: "{{ states('sensor.weekly_average_heart_points')|int }}"
tasks_list_raw: >-
{{ states('todo.blzalewski_s_list') if states('todo.blzalewski_s_list')
!= 'unknown' else '' }}
tasks_list: >
{% set raw = states('todo.blzalewski_s_list') %} {% if raw is not none
and raw != 'unknown' and raw|length > 0 %}
{{ raw.split(',') }}
{% else %}
[]
{% endif %}
weather_entity: weather.forecast_home
weather_obj: >-
{{ states(weather_entity) if states.weather.forecast_home is defined
else None }}
weather_forecast: >-
{{ weather_forecast_hourly['weather.forecast_home'].forecast if
weather_forecast_hourly['weather.forecast_home'] is defined else None }}
birthdays_start_date: |
{% set f=features %} {% if f.night_mode and now().hour|int < 24 %}
{{ tomorrow_str }}
{% else %}
{{ today_str }}
{% endif %}
birthdays_end_date: |
{% set f=features %} {% if f.night_mode and now().hour|int < 24 %}
{{ day_after_tomorrow_str }}
{% else %}
{{ tomorrow_str }}
{% endif %}
holidays_start_date: |
{% set f=features %} {% if f.night_mode and now().hour|int < 24 %}
{{ tomorrow_str }}
{% else %}
{{ today_str }}
{% endif %}
holidays_end_date: |
{% set f=features %} {% if f.night_mode and now().hour|int < 24 %}
{{ day_after_tomorrow_str }}
{% else %}
{{ tomorrow_str }}
{% endif %}
- variables:
message: |
```
{%- set hour = current_hour|int -%} {%- set f = features -%}
{# Greeting and time #} {%- if f.include_greeting_and_time %}
{%- if hour > 4 and hour < 12 %}
{%- set greeting = "Good morning! " %}
{%- elif hour >= 21 or hour <= 4 %}
{%- set greeting = "Good night! " %}
{%- elif hour < 18 %}
{%- set greeting = "Good afternoon! " %}
{%- else %}
{%- set greeting = "Good evening! " %}
{%- endif %}
{{ greeting }}
{% if not f.night_mode %}
{{ "Today is " ~ current_day_str ~ ". The current time is " ~ current_time_str ~ ". " }}
{% else %}
{{ "The current time is " ~ current_time_str ~ ". " }}
{% endif %}
{% endif %}
{# Appointments #} {% if f.include_appointments %}
{%- set appointments = namespace(events=[]) -%}
{%- for cal in appointment_calendars.values() %}
{%- set events = cal.events
| selectattr('start','match','^'~today_str~'T\\d{2}:\\d{2}:\\d{2}(?:[+\\-]\\d{2}:\\d{2})?$')
| selectattr('end','match','^'~tomorrow_str~'T\\d{2}:\\d{2}:\\d{2}(?:[+\\-]\\d{2}:\\d{2})?$')
| list -%}
{%- set appointments.events = appointments.events + events %}
{%- endfor -%}
{%- set appointments.events = appointments.events|sort(attribute='start') %}
{% if appointments.events|length > 0 %}
{% if appointments.events|length > 1 %}
{{ "You have " ~ (appointments.events|length) ~ " events scheduled in the next 24 hours. " }}
{% endif %}
{{ "Your next event starts at " ~ strptime(appointments.events[0].start, "%Y-%m-%dT%H:%M:%S%z").strftime("%-I:%M %p") ~ ". " }}
{% else %}
{{ "No scheduled events are found for the upcoming period. " }}
{% endif %}
{% endif %}
{# Holidays #} {% if f.include_holidays %}
{%- set holidays = namespace(list=[]) -%}
{%- for cal in holiday_calendars.values() %}
{%- set h = cal.events
| selectattr('start','match','^'~holidays_start_date~'$')
| selectattr('end','match','^'~holidays_end_date~'$')
| map(attribute='summary')
| list -%}
{%- set holidays.list = holidays.list + h %}
{%- endfor -%}
{%- set holidays.list = holidays.list|unique|list -%}
{% if holidays.list|length > 0 %}
{% if holidays.list|length == 1 %}
{% if f.night_mode and now().hour|int < 24 %}
{{ "Tomorrow, it will be " ~ holidays.list[0] ~ ". " }}
{% else %}
{{ "It is " ~ holidays.list[0] ~ " today. " }}
{% endif %}
{% else %}
{% if f.night_mode and now().hour|int < 24 %}
{{ "Tomorrow, it will be " ~ holidays.list[:-1]|join(", ") ~ " and " ~ holidays.list[-1] ~ ". " }}
{% else %}
{{ "It is " ~ holidays.list[:-1]|join(", ") ~ " and " ~ holidays.list[-1] ~ ". " }}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{# Birthdays #} {% if f.include_birthdays %}
{%- set birthdays = namespace(list=[]) -%}
{%- for cal in birthday_calendars.values() %}
{%- set b = cal.events
| selectattr('start','match','^'~birthdays_start_date~'$')
| selectattr('end','match','^'~birthdays_end_date~'$')
| map(attribute='summary')
| list -%}
{%- set birthdays.list = birthdays.list + b %}
{%- endfor -%}
{%- set birthdays.list = birthdays.list|unique|list -%}
{% if birthdays.list|length > 0 %}
{% if birthdays.list|length == 1 %}
{% if f.night_mode and now().hour|int < 24 %}
{{ "Tommorow is " ~ birthdays.list[0] ~ "'s birthday. " }}
{% else %}
{{ "It is " ~ birthdays.list[0] ~ "'s birthday today. Wish them a happy birthday! " }}
{% endif %}
{% else %}
{% if f.night_mode and now().hour|int < 24 %}
{{ birthdays.list[:-1]|join(", ") ~ " and " ~ birthdays.list[-1] ~ " have birthdays tomorrow. " }}
{% else %}
{{ "It is " ~ birthdays.list[:-1]|join(", ") ~ " and " ~ birthdays.list[-1] ~ "'s birthdays today. Wish them a happy birthday! " }}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{# Sleep Score #} {% if f.include_sleep_score and sleep_score_sensor is
not none and sleep_score_sensor|int >= 0 and not f.night_mode %}
{% set s = sleep_score_sensor|int %}
{% if s > 80 %}
{{ "Your sleep was excellent, scoring " ~ s ~ ". " }}
{% elif s > 70 %}
{{ "Your sleep was decent, with a score of " ~ s ~ ". " }}
{% elif s > 50 %}
{{ "Your sleep scored " ~ s ~ ", there's room for improvement. " }}
{% else %}
{{ "Your sleep was poor, scoring only " ~ s ~ ". Consider more rest tonight. " }}
{% endif %}
{% elif f.include_sleep_score %}
{{ "No sleep score data is available at this time. " }}
{% endif %}
{# Device Battery Checks #} {% if f.include_battery_checks and hour >=5
and hour <=14 %}
{% if phone_battery_sensor is not none and phone_battery_sensor|int < 50 %}
{{ "Your phone battery is at " ~ phone_battery_sensor ~ "%. A charge is advisable. " }}
{% endif %}
{% if watch_battery_sensor is not none and watch_battery_sensor|int < 50 %}
{{ "Your watch battery is at " ~ watch_battery_sensor ~ "%. Consider plugging it in. " }}
{% endif %}
{% endif %}
{# Weather Alerts #} {% if f.include_weather_alerts and morris_sensor is
not none and morris_sensor|int >= 1 %}
{% if chatgpt_alert_summary is defined %}
{{ "There is an active weather alert: " ~ chatgpt_alert_summary.text ~ ". " }}
{% else %}
{{ "A weather alert is active, but no details are provided. Stay cautious. " }}
{% endif %}
{% endif %}
{# Weather Forecast #} {% if f.include_weather and weather_obj is not
none %}
{% set temp = state_attr(weather_entity,'temperature')|int %}
{% set tempunit = state_attr(weather_entity,'temperature_unit')|default('°F') %}
{% set humidity = state_attr(weather_entity,'humidity')|int %}
{% set sunset = states('sensor.sun_next_setting')|as_timestamp|timestamp_custom('%-I:%M %p') %}
{{ "Currently, it's " ~ temp ~ " " ~ tempunit ~ " with " ~ humidity ~ "% humidity. " }}
{% if humidity > 80 %}
{{ "High humidity may feel heavy—light clothing helps. " }}
{% elif humidity < 30 %}
{{ "The air is dry; consider moisturizer or a humidifier. " }}
{% elif humidity > 65 and humidity <= 80 %}
{{ "Moderate humidity may make it feel slightly warmer. " }}
{% endif %}
{% if weather_forecast %}
{% set temperatures = weather_forecast|map(attribute='temperature')|list %}
{% set temphigh = temperatures|max if temperatures else temp %}
{% set templow = temperatures|min if temperatures else temp %}
{% set precips = weather_forecast|map(attribute='precipitation')|select('defined')|list %}
{% set total_precip = precips|sum if precips else 0 %}
{% set precip_probs = weather_forecast|map(attribute='precipitation_probability')|select('defined')|list %}
{% set max_precip_chance = precip_probs|max if precip_probs else 0 %}
{% set conditions = weather_forecast|map(attribute='condition')|select('defined')|list %}
{% set conditions = conditions[:12] %}
{{ "The high may reach around " ~ temphigh ~ " " ~ tempunit ~ " and the low near " ~ templow ~ " " ~ tempunit ~ ". " }}
{% set total_count = conditions|length %}
{% set threshold = total_count * 0.1 %}
{% set ns = namespace(keep=[]) %}
{% for c in conditions|unique %}
{% set count = (conditions|select('equalto', c)|list|length) %}
{% if count >= threshold %}
{% set ns.keep = ns.keep + [c] %}
{% endif %}
{% endfor %}
{% set common_conditions = (conditions|select('in', ns.keep)|unique)|list %}
{% set conditions_map = {
'rainy': "frequent rain showers",
'snowy': "periods of snow",
'foggy': "occasional fog",
'sunny': "some sunshine",
'clear': "clear skies",
'clear-night': "clear skies",
'partlycloudy': "a mix of sun and clouds",
'cloudy': "overcast skies",
'sleet': "some sleet",
'hail': "hail",
'lightning': "lightning",
'lightning-rainy': "thunderstorms",
'pouring': "heavy rainfall",
'snowy-rainy': "mixed snow and rain",
'exceptional': "unusual weather"
} %}
{% set wind_speeds = weather_forecast|map(attribute='wind_speed')|select('defined')|list %}
{% set max_wind_speed = wind_speeds|max if wind_speeds else 0 %}
{% set summary_parts = [] %}
{% for c, desc in conditions_map.items() %}
{% if c in common_conditions %}
{% set summary_parts = summary_parts + [desc] %}
{% endif %}
{% endfor %}
{% if summary_parts|length == 0 %}
{% set summary_parts = ["variable conditions"] %}
{% endif %}
{% if max_precip_chance > 50 and total_precip > 0 %}
{% set summary_parts = summary_parts + ["some measurable precipitation"] %}
{% elif max_precip_chance > 50 %}
{% set summary_parts = summary_parts + ["a notable chance of precipitation"] %}
{% endif %}
{% if max_wind_speed > 20 %}
{% set summary_parts = summary_parts + ["brisk winds"] %}
{% elif max_wind_speed > 10 %}
{% set summary_parts = summary_parts + ["gentle breezes"] %}
{% endif %}
{% if summary_parts|length > 1 %}
{{ "You can anticipate " ~ summary_parts[:-1]|join(", ") ~ " and " ~ summary_parts[-1] ~ " ahead. " }}
{% else %}
{{ "You can expect " ~ summary_parts[0] ~ " ahead. " }}
{% endif %}
{% if total_precip > 0.8 %}
{{ "Heavy precipitation is likely. " }}
{% endif %}
{% if 'rainy' in common_conditions and max_precip_chance > 40 %}
{{ "Rain is quite possible—carry an umbrella. " }}
{% endif %}
{% if temphigh > 95 %}
{{ "Extreme heat—stay cool and hydrated. " }}
{% endif %}
{% if temphigh < 30 %}
{{ "Very cold—dress warmly if venturing out. " }}
{% endif %}
{% if total_precip > 0.2 and total_precip <= 0.8 %}
{{ "Light, intermittent precipitation may occur. " }}
{% endif %}
{% if max_precip_chance > 70 and 'rainy' not in common_conditions and 'snowy' not in common_conditions %}
{{ "Unusual precipitation like sleet or hail may appear—be alert. " }}
{% endif %}
{% if 'sunny' in common_conditions and temphigh > 85 %}
{{ "Strong sunshine—consider sunscreen and protective gear. " }}
{% endif %}
{% if temphigh > 60 and temphigh < 80 and ('sunny' in common_conditions or 'partlycloudy' in common_conditions or 'cloudy' in common_conditions) %}
{{ "Conditions are mild and comfortable, suitable for various activities. " }}
{% endif %}
{% if humidity > 80 and temphigh > 85 %}
{{ "High heat and humidity—take indoor breaks and stay hydrated. " }}
{% endif %}
{% if 'rainy' in common_conditions and temphigh < 60 %}
{{ "Cool and rainy—grab a jacket and waterproof footwear. " }}
{% endif %}
{% if 'snowy' in common_conditions and max_wind_speed > 15 %}
{{ "Snow with moderate winds—caution on roads. " }}
{% endif %}
{% if temphigh < 32 %}
{{ "Below freezing—watch for ice. " }}
{% endif %}
{% if temphigh < 20 %}
{{ "Extremely cold—risk of frostbite, dress in layers. " }}
{% endif %}
{% if max_wind_speed > 25 %}
{{ "Strong gusts possible—secure outdoor items. " }}
{% endif %}
{% if humidity < 30 and max_wind_speed > 15 %}
{{ "Dry and breezy—use moisturizer for comfort. " }}
{% endif %}
{% if ('cloudy' in common_conditions or 'partlycloudy' in common_conditions) and max_precip_chance < 30 and temphigh > 50 and temphigh < 85 %}
{{ "Cloud cover with mild temps—pleasant for most plans. " }}
{% endif %}
{% if humidity > 40 and humidity < 60 and max_wind_speed < 10 %}
{{ "Balanced humidity and light winds—overall pleasant. " }}
{% endif %}
{{ "Sunset is around " ~ sunset ~ ". " }}
{% else %}
{{ "Detailed hourly forecast data isn't currently available. " }}
{% endif %}
{% elif f.include_weather %}
{{ "Weather information is currently unavailable. " }}
{% endif %}
{# Zodiac Sign #} {% if f.include_zodiac and zodiac_today_sensor !=
'unknown' and zodiac_yesterday_sensor != 'unknown' and
zodiac_today_sensor != zodiac_yesterday_sensor %}
{{ "The Zodiac sign has shifted from " ~ zodiac_yesterday_sensor ~ " to " ~ zodiac_today_sensor ~ " today. " }}
{% endif %}
{# Full Moon #} {% if f.include_full_moon and moon_phase_sensor ==
'full_moon' %}
{{ "Tonight features a full moon—take a moment to appreciate its glow. " }}
{% endif %}
{# Sunday Reminder #} {% if f.include_sunday_reminder and weekday ==
'Sunday' %}
{{ "Remember to water your plants today since it's Sunday. " }}
{% endif %}
{# Tasks Section #} {% if f.include_tasks %}
{%- set tasks = tasks_list %}
{% if tasks|length > 0 %}
{{ "Here are the tasks on your to-do list: " }}
{% for t in tasks %}
{{ "- " ~ t ~ " " }}
{% endfor %}
{% else %}
{{ "Your to-do list appears to be empty at the moment. " }}
{% endif %}
{% endif %}
{# Air Quality #} {% if f.include_air_quality and
local_air_quality_sensor in ['Unhealthy for sensitive groups',
'Unhealthy', 'Very unhealthy', 'Hazardous'] %}
{{ "Local outdoor air quality is " ~ local_air_quality_sensor ~ ", consider limiting extended outdoor activity. " }}
{% endif %}
{# Re-include Smart Predictions Section #} {% if
f.include_smart_predictions %}
{# Steps comparison #}
{% if today_steps > 0 and avg_steps > 0 %}
{% if today_steps < avg_steps %}
{{ "You have fewer steps than your weekly average so far. A short walk might help. " }}
{% else %}
{{ "Your steps match or exceed your weekly average—keep the momentum! " }}
{% endif %}
{% endif %}
{# Heart Rate comparison #}
{% if today_hr > 0 and avg_hr > 0 %}
{% if today_hr > avg_hr + 5 %}
{{ "Your average heart rate is higher than usual. A calm break might be beneficial. " }}
{% elif today_hr < avg_hr - 5 %}
{{ "Your heart rate is lower than normal, possibly indicating good rest. " }}
{% else %}
{{ "Your heart rate is near your normal baseline. Stay steady. " }}
{% endif %}
{% endif %}
{# Resting Heart Rate comparison #}
{% if today_resting_hr > 0 and avg_resting_hr > 0 %}
{% if today_resting_hr > avg_resting_hr + 5 %}
{{ "Your resting heart rate is above average. Consider a brief relaxation technique. " }}
{% elif today_resting_hr < avg_resting_hr - 5 %}
{{ "Your resting heart rate is lower than usual, indicating positive recovery. " }}
{% else %}
{{ "Your resting heart rate is stable. Maintain your current healthy habits. " }}
{% endif %}
{% endif %}
{# Oxygen Saturation comparison #}
{% if today_oxygen > 0 and avg_oxygen > 0 %}
{% if today_oxygen < avg_oxygen - 1.0 %}
{{ "Your oxygen saturation is slightly lower today—consider a deep-breathing pause. " }}
{% else %}
{{ "Your oxygen saturation aligns with your weekly norm. " }}
{% endif %}
{% endif %}
{# Blood Glucose comparison #}
{% if today_bglucose > 0 and avg_bglucose > 0 %}
{% if today_bglucose > avg_bglucose + 10 %}
{{ "Your blood glucose is higher than average—monitor your intake or consider guidance. " }}
{% elif today_bglucose < avg_bglucose - 10 %}
{{ "Your blood glucose is lower than usual. If you feel off, a small balanced snack might help. " }}
{% else %}
{{ "Your blood glucose remains close to your weekly average. " }}
{% endif %}
{% endif %}
{# Heart Points comparison #}
{% if today_heart_points > 0 and avg_heart_points > 0 %}
{% if today_heart_points < avg_heart_points %}
{{ "Your heart points are lower than normal. Maybe add a light cardio activity today. " }}
{% else %}
{{ "Your heart points are at or above your usual level—great job staying active! " }}
{% endif %}
{% endif %}
{% endif %}
{# Motivational Quote at the end #} {% if f.include_motivational_quotes
%}
{%- set motivational_quotes = [
"Seize your opportunities today.",
"Each hour can bring growth and progress.",
"Keep moving forward with hope.",
"Every step counts toward your goals.",
"Embrace the moment and shine."
] -%}
{%- set quote = motivational_quotes|random -%}
{{ "Here's an encouraging thought: " ~ quote }}
{% endif %}
```
- action: google_generative_ai_conversation.generate_content
data:
prompt: >
Please take the following daily summary message and:
1. Improve grammar, phrasing, and style. 2. Add day-specific historical
significance or seasonal notes only if the information is highly
relevant (e.g., if it's a historically significant date or a leap day).
Include this addition at the end of the response and make it as short
and concise as possible. Unless the information is very significant or
highly relevant, then just skip this step 3. Maintain all reported facts
and data, following the same structure with minor adjustments to improve
hearability 4. Provide subtle reminders about why this date may matter
(e.g., historical events). 5. Remove all emojis prior to processing 6.
Make it as concise and short as possible 6. Remove all astericks from
the response
Original Message: {{ message }}
response_variable: refined_message
- action: tts.speak
data:
media_player_entity_id: media_player.living_room
message: "{{ refined_message.text }}"
target:
entity_id: tts.google_cloud