Here is a rough example, I just copied the alarm_webhook actions, probably not correct but as a proof of concept it works, lol.
alias: Weather Test
description: ""
triggers:
- trigger: time_pattern
hours: /3
minutes: "3"
id: time_based
- trigger: state
entity_id: []
to: "on"
id: sensor_trigger
- trigger: state
entity_id: weather.forecast_home
id: current_change
- trigger: time_pattern
minutes: /5
id: upcoming_change
- trigger: webhook
allowed_methods:
- POST
- PUT
- GET
- HEAD
local_only: true
webhook_id: wakeup_alarm
id: alarm_webhook
- trigger: conversation
command:
- weather report
- what's the weather like
id: sentence
conditions:
- condition: or
conditions:
- condition: and
conditions:
- condition: trigger
id: time_based
- condition: time
after: "08:00:00"
before: "21:00:00"
- condition: template
value_template: "{{ enable_time_based }}"
- condition: template
value_template: >-
{{ not days_of_week or now().strftime('%a').lower()[:3] in
days_of_week }}
- condition: and
conditions:
- condition: trigger
id: sensor_trigger
- condition: template
value_template: "{{ enable_sensor_triggered }}"
- condition: and
conditions:
- condition: trigger
id: current_change
- condition: template
value_template: "{{ enable_current_change_announce }}"
- condition: and
conditions:
- condition: trigger
id: upcoming_change
- condition: template
value_template: "{{ enable_upcoming_change_announce }}"
- condition: and
conditions:
- condition: trigger
id: alarm_webhook
- condition: template
value_template: "{{ enable_alarm_announce }}"
- condition: trigger
id:
- sentence
actions:
- variables:
key: weather.forecast_home
spoken: ""
- choose:
- conditions:
- condition: or
conditions:
- condition: trigger
id: time_based
- condition: trigger
id: sensor_trigger
sequence:
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: daily
response_variable: d
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: hourly
response_variable: h
- variables:
precip_threshold: 30
hours_ahead: 24
now_hour: "{{ now().hour }}"
day_name: "{{ (now() | as_timestamp | timestamp_custom('%A', true)) }}"
greeting: >-
{% if 5 <= now_hour <= 11 %}Good morning{% elif 12 <= now_hour
<= 17 %} Good afternoon{% else %}Good evening{% endif %}
now_temp: "{{ state_attr(key,'temperature') }}"
now_hum: "{{ state_attr(key,'humidity') }}"
now_cond_h: >-
{% set s = (states(key) or '') | lower %} {% set s = s |
replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
daily_list: "{{ d.get(key,{}).get('forecast',[]) }}"
today: "{{ daily_list[0] if daily_list|length>0 else dict() }}"
high: "{{ today.get('temperature') }}"
low: "{{ today.get('templow', today.get('temperature_low')) }}"
today_cond_h: >-
{% set raw = (today.get('condition') or states(key) or '') |
lower %} {% set s = raw | replace('_',' ') | replace('-',' ') %}
{% set s = s | replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
now_norm: "{{ now_cond_h | replace(' ','') }}"
today_norm: "{{ today_cond_h | replace(' ','') }}"
mention_expect: "{{ today_cond_h != '' and today_norm != now_norm }}"
hourly_list: "{{ h.get(key,{}).get('forecast',[]) }}"
next_hours: "{{ hourly_list[:hours_ahead] if hourly_list else [] }}"
hits: >-
{{ (next_hours |
selectattr('precipitation_probability','defined') |
selectattr('precipitation_probability','ge', precip_threshold) |
list) }}
first_hit: "{{ hits[0] if hits|length>0 else dict() }}"
hit_pp: "{{ first_hit.get('precipitation_probability') }}"
hit_cond_h: >-
{% set raw = (first_hit.get('condition','') | lower) %} {% set s
= raw | replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
hit_ts: >-
{% set t = none %} {% if first_hit.get('datetime') %} {% set t =
as_datetime(first_hit.get('datetime')) | as_timestamp %} {%
endif %} {{ t }}
hit_clock: >-
{% set out = '' %} {% if hit_ts is not none %} {% set out =
(hit_ts | timestamp_custom('%I %p', true)) | replace(' 0',' ')
%} {% endif %} {{ out }}
hit_hour_num: >-
{% set out = 0 %} {% if hit_ts is not none %} {% set out =
(hit_ts | timestamp_custom('%H', true)) | int %} {% endif %} {{
out }}
hit_daypart: >-
{% set hnum = hit_hour_num %} {% if 5 <= hnum <= 11 %}this
morning{% elif 12 <= hnum <= 17 %}this afternoon {% elif 18 <=
hnum <= 22 %}this evening{% else %}overnight{% endif %}
precip_kind: >-
{% if 'snow' in hit_cond_h %}snow{% elif 'sleet' in hit_cond_h
or 'hail' in hit_cond_h %}mixed precipitation {% elif hit_cond_h
%}rain{% else %}precipitation{% endif %}
msg: >-
{% set p = [] %} {% set p = p + [greeting ~ '! ' ~ day_name ~
"'s forecast."] %} {% set now_bits = [] %} {% if now_cond_h %}{%
set now_bits = now_bits + [now_cond_h | capitalize] %}{% endif
%} {% if now_temp is not none %}{% set now_bits = now_bits + [
(now_temp | round(0) ~ ' degrees') ] %}{% endif %} {% if now_hum
is not none %}{% set now_bits = now_bits + [ ('humidity ' ~
now_hum ~ '%') ] %}{% endif %} {% if now_bits | length > 0 %}{%
set p = p + ['Right now: ' ~ (now_bits | join(', ')) ~ '.'] %}{%
endif %} {% set today_bits = [] %} {% if mention_expect %}{% set
today_bits = today_bits + ['Expect ' ~ today_cond_h ~ '.'] %}{%
endif %} {% if high is not none %}{% set today_bits = today_bits
+ ['High ' ~ (high | round(0)) ~ ' degrees.'] %}{% endif %} {%
if low is not none %}{% set today_bits = today_bits + ['Low ' ~
(low | round(0)) ~ ' degrees.'] %}{% endif %} {% if today_bits |
length > 0 %}{% set p = p + [ today_bits | join(' ') ] %}{%
endif %} {% if hits | length > 0 and hit_pp is not none %} {%
set phr = precip_kind ~ ' chances ' ~ hit_daypart %} {% if
hit_clock %}{% set phr = phr ~ ' around ' ~ hit_clock %}{% endif
%} {% set phr = phr ~ ': ' ~ (hit_pp | int) ~ '%.' %} {% set p =
p + [ phr ] %} {% endif %} {{ p | join(' ') if p|length > 0 else
'Forecast not available.' }}
i_ai: false
i_ai_task: ""
i_ai_text: >
Rewrite the message below for clear, natural TTS in a crisp
TV-meteorologist voice.
Hard rules:
- Preserve every fact exactly: all numbers, units (degrees, %,
mph), times, day names, and weather terms.
- Do not invent, omit, reorder, round, or restate any facts.
Keep original sequencing of info.
- Output RAW TEXT ONLY — no quotes, code fences, YAML/JSON,
greetings, preambles, or sign-offs.
- 2–4 short sentences, ≤55 words, plain vocabulary, no emojis,
no hashtags, no ALL CAPS.
- If precipitation timing/probability exists, mention it once
using the same daypart/time and %; otherwise omit.
- Keep symbols and formatting as given (%, AM/PM). If the input
is “Forecast not available.” return it unchanged.
- choose:
- conditions:
- condition: template
value_template: "{{ i_ai and (i_ai_task | default('') | string) != '' }}"
sequence:
- action: ai_task.generate_data
data:
entity_id: "{{ i_ai_task }}"
task_name: weather message
instructions: |-
{{ i_ai_text }}
Message:
"{{ msg | replace('"','\\"') }}"
response_variable: aimsg
- variables:
spoken: "{{ aimsg.data | default(msg) }}"
default:
- variables:
spoken: "{{ msg }}"
- action: media_player.volume_set
target:
entity_id:
- media_player.home_assistant_voice_0a2aa1_media_player
data:
volume_level: 0.6
- repeat:
for_each:
- media_player.home_assistant_voice_0a2aa1_media_player
sequence:
- delay:
milliseconds: 150
- action: tts.speak
target:
entity_id: tts.home_assistant_cloud
data:
cache: true
media_player_entity_id: "{{ repeat.item }}"
message: "{{ spoken | trim }}"
options:
voice: en_US-carlin-high
- conditions:
- condition: trigger
id: current_change
sequence:
- variables:
now_cond_raw: "{{ trigger.to_state.state | lower }}"
now_cond_h: >-
{% set s = now_cond_raw %} {% set s = s | replace('_',' ') |
replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
from_cond_raw: "{{ trigger.from_state.state | default('unknown') | lower }}"
now_temp: "{{ trigger.to_state.attributes.temperature | round(0) }}"
is_change: "{{ now_cond_raw != from_cond_raw }}"
msg: >-
Weather update: It's now {{ now_temp }} degrees and {{
now_cond_h }}.
i_ai: false
i_ai_task: ""
i_ai_text: >
Rewrite the message below for clear, natural TTS in a crisp
TV-meteorologist voice.
Hard rules:
- Preserve every fact exactly: all numbers, units (degrees, %,
mph), times, day names, and weather terms.
- Do not invent, omit, reorder, round, or restate any facts.
Keep original sequencing of info.
- Output RAW TEXT ONLY — no quotes, code fences, YAML/JSON,
greetings, preambles, or sign-offs.
- 2–4 short sentences, ≤55 words, plain vocabulary, no emojis,
no hashtags, no ALL CAPS.
- If precipitation timing/probability exists, mention it once
using the same daypart/time and %; otherwise omit.
- Keep symbols and formatting as given (%, AM/PM). If the input
is “Forecast not available.” return it unchanged.
- if:
- condition: template
value_template: "{{ is_change }}"
then:
- choose:
- conditions:
- condition: template
value_template: >-
{{ i_ai and (i_ai_task | default('') | string) != ''
}}
sequence:
- action: ai_task.generate_data
data:
entity_id: "{{ i_ai_task }}"
task_name: weather message
instructions: |-
{{ i_ai_text }}
Message:
"{{ msg | replace('"','\\"') }}"
response_variable: aimsg
- variables:
spoken: "{{ aimsg.data | default(msg) }}"
default:
- variables:
spoken: "{{ msg }}"
- action: media_player.volume_set
target:
entity_id:
- media_player.home_assistant_voice_0a2aa1_media_player
data:
volume_level: 0.6
- repeat:
for_each:
- media_player.home_assistant_voice_0a2aa1_media_player
sequence:
- delay:
milliseconds: 150
- action: tts.speak
target:
entity_id: tts.home_assistant_cloud
data:
cache: true
media_player_entity_id: "{{ repeat.item }}"
message: "{{ spoken | trim }}"
options:
voice: en_US-carlin-high
- conditions:
- condition: trigger
id: upcoming_change
sequence:
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: hourly
response_variable: h
- variables:
hourly_list: "{{ h.get(key,{}).get('forecast',[]) }}"
upcoming: >-
{{ hourly_list | selectattr('datetime','gt', now().isoformat())
| sort(attribute='datetime') | list }}
next_hit: "{{ upcoming[0] if upcoming else {} }}"
next_dt: "{{ next_hit.get('datetime') }}"
next_ts: "{{ as_timestamp(next_dt) if next_dt else none }}"
min_to: >-
{{ ((next_ts - as_timestamp(now())) / 60) | round(0) if next_ts
else 0 }}
next_cond_raw: "{{ next_hit.get('condition','') | lower }}"
next_cond_h: >-
{% set s = next_cond_raw %} {% set s = s | replace('_',' ') |
replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
now_cond_raw: "{{ states(key) | lower }}"
now_is_precip: >-
{{ 'rain' in now_cond_raw or 'snow' in now_cond_raw or 'hail' in
now_cond_raw or 'sleet' in now_cond_raw or 'pour' in
now_cond_raw or 'lightning' in now_cond_raw }}
next_is_precip: >-
{{ 'rain' in next_cond_raw or 'snow' in next_cond_raw or 'hail'
in next_cond_raw or 'sleet' in next_cond_raw or 'pour' in
next_cond_raw or 'lightning' in next_cond_raw }}
should_announce: >-
{{ next_ts is not none and next_is_precip and not now_is_precip
and 0 < min_to <= minutes_before_announce }}
precip_kind: >-
{% if 'snow' in next_cond_h %}snow{% elif 'sleet' in next_cond_h
or 'hail' in next_cond_h %}mixed precipitation {% elif
next_cond_h %}rain{% else %}precipitation{% endif %}
msg: >-
Weather alert: {{ precip_kind }} expected in about {{ min_to }}
minutes.
i_ai: false
i_ai_task: ""
i_ai_text: >
Rewrite the message below for clear, natural TTS in a crisp
TV-meteorologist voice.
Hard rules:
- Preserve every fact exactly: all numbers, units (degrees, %,
mph), times, day names, and weather terms.
- Do not invent, omit, reorder, round, or restate any facts.
Keep original sequencing of info.
- Output RAW TEXT ONLY — no quotes, code fences, YAML/JSON,
greetings, preambles, or sign-offs.
- 2–4 short sentences, ≤55 words, plain vocabulary, no emojis,
no hashtags, no ALL CAPS.
- If precipitation timing/probability exists, mention it once
using the same daypart/time and %; otherwise omit.
- Keep symbols and formatting as given (%, AM/PM). If the input
is “Forecast not available.” return it unchanged.
- if:
- condition: template
value_template: "{{ should_announce }}"
then:
- choose:
- conditions:
- condition: template
value_template: >-
{{ i_ai and (i_ai_task | default('') | string) != ''
}}
sequence:
- action: ai_task.generate_data
data:
entity_id: "{{ i_ai_task }}"
task_name: weather message
instructions: |-
{{ i_ai_text }}
Message:
"{{ msg | replace('"','\\"') }}"
response_variable: aimsg
- variables:
spoken: "{{ aimsg.data | default(msg) }}"
default:
- variables:
spoken: "{{ msg }}"
- action: media_player.volume_set
target:
entity_id:
- media_player.home_assistant_voice_0a2aa1_media_player
data:
volume_level: 0.6
- repeat:
for_each:
- media_player.home_assistant_voice_0a2aa1_media_player
sequence:
- delay:
milliseconds: 150
- action: tts.speak
target:
entity_id: tts.home_assistant_cloud
data:
cache: true
media_player_entity_id: "{{ repeat.item }}"
message: "{{ spoken | trim }}"
options:
voice: en_US-carlin-high
- conditions:
- condition: trigger
id: alarm_webhook
sequence:
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: daily
response_variable: d
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: hourly
response_variable: h
- variables:
name: ""
current_time: >-
{{ as_timestamp(now()) | timestamp_custom('%I:%M %p', true) |
replace(' 0', ' ') }}
now_temp: "{{ state_attr(key,'temperature') | round(0) }}"
now_cond_h: >-
{% set s = (states(key) or '') | lower %} {% set s = s |
replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
daily_list: "{{ d.get(key,{}).get('forecast',[]) }}"
today: "{{ daily_list[0] if daily_list|length>0 else dict() }}"
high: "{{ today.get('temperature') | round(0) }}"
low: >-
{{ today.get('templow', today.get('temperature_low')) | round(0)
}}
hourly_list: "{{ h.get(key,{}).get('forecast',[]) }}"
hits: >-
{{ (hourly_list |
selectattr('precipitation_probability','defined') |
selectattr('precipitation_probability','ge', precip_threshold) |
list) }}
first_hit: "{{ hits[0] if hits|length>0 else dict() }}"
hit_pp: >-
{{ first_hit.get('precipitation_probability') | int(default=0)
}}
hit_cond_h: >-
{% set raw = (first_hit.get('condition','') | lower) %} {% set s
= raw | replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
hit_ts: >-
{% set t = none %} {% if first_hit.get('datetime') %} {% set t =
as_datetime(first_hit.get('datetime')) | as_timestamp %} {%
endif %} {{ t }}
hit_clock: >-
{% set out = '' %} {% if hit_ts is not none %} {% set out =
(hit_ts | timestamp_custom('%I %p', true)) | replace(' 0',' ')
%} {% endif %} {{ out }}
precip_kind: >-
{% if 'snow' in hit_cond_h %}snow{% elif 'sleet' in hit_cond_h
or 'hail' in hit_cond_h %}mixed precipitation {% elif hit_cond_h
%}rain{% else %}precipitation{% endif %}
precip_msg: >-
{% if hits | length > 0 and hit_pp > 0 %}expect {{ precip_kind
}} later in the day around {{ hit_clock }} with a {{ hit_pp }}%
chance.{% else %}{% endif %}
msg: >-
here's your forecast. It's {{ current_time }}. Currently {{
now_temp }} degrees and {{ now_cond_h }}. {{ precip_msg }} High
{{ high }} degrees. Low {{ low }} degrees.
i_ai: false
i_ai_task: ""
i_ai_text: >
Rewrite the message below for clear, natural TTS in a crisp
TV-meteorologist voice.
Hard rules:
- Preserve every fact exactly: all numbers, units (degrees, %,
mph), times, day names, and weather terms.
- Do not invent, omit, reorder, round, or restate any facts.
Keep original sequencing of info.
- Output RAW TEXT ONLY — no quotes, code fences, YAML/JSON,
greetings, preambles, or sign-offs.
- 2–4 short sentences, ≤55 words, plain vocabulary, no emojis,
no hashtags, no ALL CAPS.
- If precipitation timing/probability exists, mention it once
using the same daypart/time and %; otherwise omit.
- Keep symbols and formatting as given (%, AM/PM). If the input
is “Forecast not available.” return it unchanged.
- choose:
- conditions:
- condition: template
value_template: "{{ i_ai and (i_ai_task | default('') | string) != '' }}"
sequence:
- action: ai_task.generate_data
data:
entity_id: "{{ i_ai_task }}"
task_name: weather message
instructions: |-
{{ i_ai_text }}
Message:
"{{ msg | replace('"','\\"') }}"
response_variable: aimsg
- variables:
spoken: "{{ aimsg.data | default(msg) }}"
default:
- variables:
spoken: "{{ msg }}"
- variables:
spoken_final: Good morning {{ name }}, {{ spoken }}
- action: media_player.volume_set
target:
entity_id:
- media_player.home_assistant_voice_0a2aa1_media_player
data:
volume_level: 0.6
- repeat:
for_each:
- media_player.home_assistant_voice_0a2aa1_media_player
sequence:
- delay:
milliseconds: 150
- action: tts.speak
target:
entity_id: tts.home_assistant_cloud
data:
cache: true
media_player_entity_id: "{{ repeat.item }}"
message: "{{ spoken_final | trim }}"
options:
voice: en_US-carlin-high
- conditions:
- condition: trigger
id:
- sentence
sequence:
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: daily
response_variable: d
- action: weather.get_forecasts
target:
entity_id: weather.forecast_home
data:
type: hourly
response_variable: h
- variables:
name: ""
current_time: >-
{{ as_timestamp(now()) | timestamp_custom('%I:%M %p', true) |
replace(' 0', ' ') }}
now_temp: "{{ state_attr(key,'temperature') | round(0) }}"
now_cond_h: >-
{% set s = (states(key) or '') | lower %} {% set s = s |
replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
daily_list: "{{ d.get(key,{}).get('forecast',[]) }}"
today: "{{ daily_list[0] if daily_list|length>0 else dict() }}"
high: "{{ today.get('temperature') | round(0) }}"
low: >-
{{ today.get('templow', today.get('temperature_low')) | round(0)
}}
hourly_list: "{{ h.get(key,{}).get('forecast',[]) }}"
hits: >-
{{ (hourly_list |
selectattr('precipitation_probability','defined') |
selectattr('precipitation_probability','ge', precip_threshold) |
list) }}
first_hit: "{{ hits[0] if hits|length>0 else dict() }}"
hit_pp: >-
{{ first_hit.get('precipitation_probability') | int(default=0)
}}
hit_cond_h: >-
{% set raw = (first_hit.get('condition','') | lower) %} {% set s
= raw | replace('_',' ') | replace('-',' ') %} {% set s = s |
replace('partlycloudy','partly cloudy') |
replace('clearnight','clear night') %} {{ s | trim }}
hit_ts: >-
{% set t = none %} {% if first_hit.get('datetime') %} {% set t =
as_datetime(first_hit.get('datetime')) | as_timestamp %} {%
endif %} {{ t }}
hit_clock: >-
{% set out = '' %} {% if hit_ts is not none %} {% set out =
(hit_ts | timestamp_custom('%I %p', true)) | replace(' 0',' ')
%} {% endif %} {{ out }}
precip_kind: >-
{% if 'snow' in hit_cond_h %}snow{% elif 'sleet' in hit_cond_h
or 'hail' in hit_cond_h %}mixed precipitation {% elif hit_cond_h
%}rain{% else %}precipitation{% endif %}
precip_msg: >-
{% if hits | length > 0 and hit_pp > 0 %}expect {{ precip_kind
}} later in the day around {{ hit_clock }} with a {{ hit_pp }}%
chance.{% else %}{% endif %}
msg: >-
here's your forecast. It's {{ current_time }}. Currently {{
now_temp }} degrees and {{ now_cond_h }}. {{ precip_msg }} High
{{ high }} degrees. Low {{ low }} degrees.
i_ai: false
i_ai_task: ""
i_ai_text: >
Rewrite the message below for clear, natural TTS in a crisp
TV-meteorologist voice.
Hard rules:
- Preserve every fact exactly: all numbers, units (degrees, %,
mph), times, day names, and weather terms.
- Do not invent, omit, reorder, round, or restate any facts.
Keep original sequencing of info.
- Output RAW TEXT ONLY — no quotes, code fences, YAML/JSON,
greetings, preambles, or sign-offs.
- 2–4 short sentences, ≤55 words, plain vocabulary, no emojis,
no hashtags, no ALL CAPS.
- If precipitation timing/probability exists, mention it once
using the same daypart/time and %; otherwise omit.
- Keep symbols and formatting as given (%, AM/PM). If the input
is “Forecast not available.” return it unchanged.
- choose:
- conditions:
- condition: template
value_template: "{{ i_ai and (i_ai_task | default('') | string) != '' }}"
sequence:
- action: ai_task.generate_data
data:
entity_id: "{{ i_ai_task }}"
task_name: weather message
instructions: |-
{{ i_ai_text }}
Message:
"{{ msg | replace('"','\\"') }}"
response_variable: aimsg
- variables:
spoken: "{{ aimsg.data | default(msg) }}"
default:
- variables:
spoken: "{{ msg }}"
- variables:
spoken_final: Good morning {{ name }}, {{ spoken }}
- set_conversation_response: "{{ spoken_final | trim }}"
trigger_variables:
enable_time_based: false
enable_sensor_triggered: false
enable_current_change_announce: true
enable_upcoming_change_announce: true
minutes_before_announce: 30
enable_alarm_announce: false
mode: single
Oh, and one more thing. I noticed the run mode was set to single. This could be an issue. For example, if the blueprint is being triggered by the every 5 minute trigger, then it might prevent a run from another trigger. Then again, that may be by design.