Update HA version badge to 2026.3.2

pull/1639/head
github-actions[bot] 3 days ago committed by Carlo Costanzo
parent 8bf02a76ff
commit 5964246466

2
.gitignore vendored

@ -70,6 +70,7 @@ hacs
alexa_media alexa_media
custom_components custom_components
config/www/community config/www/community
config/.cache/brands/integrations/
community community
image image
tts tts
@ -80,3 +81,4 @@ panel-notes
docker_14 docker_14
docker_69 docker_69
.codex_tmp/ .codex_tmp/

@ -59,6 +59,7 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [vacation_mode.yaml](vacation_mode.yaml) | Auto-enable vacation mode after 24 hours away or no bed use, track sitter analytics/secure-house checks, and deliver Chromecast-first vacation briefings with a garage Alexa welcome. | `input_boolean.vacation_mode`, `input_boolean.house_sitter_present`, `sensor.vacation_house_sitter_*`, `group.garage_doors`, `lock.front_door`, `script.notify_engine`, `script.joanna_send_telegram` | | [vacation_mode.yaml](vacation_mode.yaml) | Auto-enable vacation mode after 24 hours away or no bed use, track sitter analytics/secure-house checks, and deliver Chromecast-first vacation briefings with a garage Alexa welcome. | `input_boolean.vacation_mode`, `input_boolean.house_sitter_present`, `sensor.vacation_house_sitter_*`, `group.garage_doors`, `lock.front_door`, `script.notify_engine`, `script.joanna_send_telegram` |
| [maintenance_log.yaml](maintenance_log.yaml) | Joanna maintenance webhook ingest for water softener salt with idempotent event handling, Activity feed logging, and recorder-backed helper history for long-term graphing. | `automation.maintenance_log_joanna_webhook_ingest`, `input_number.water_softener_salt_total_added_lb`, `counter.water_softener_salt_event_count`, `sensor.water_softener_salt_days_since_last_add` | | [maintenance_log.yaml](maintenance_log.yaml) | Joanna maintenance webhook ingest for water softener salt with idempotent event handling, Activity feed logging, and recorder-backed helper history for long-term graphing. | `automation.maintenance_log_joanna_webhook_ingest`, `input_number.water_softener_salt_total_added_lb`, `counter.water_softener_salt_event_count`, `sensor.water_softener_salt_days_since_last_add` |
| [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status and shed loads automatically when off-grid (alerts include Activity feed + Repairs). | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `repairs.create` | | [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status and shed loads automatically when off-grid (alerts include Activity feed + Repairs). | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `repairs.create` |
| [tesla_model_y.yaml](tesla_model_y.yaml) | Remind the garage and parents to plug in the Model Y after low-battery arrivals and after 8 PM when it is home but not charging. | `sensor.spaceship_battery_level`, `switch.spaceship_charge`, `notify.alexa_media_garage`, `script.notify_engine` |
| [vacuum.yaml](vacuum.yaml) | Dreame vacuum orchestration with room tracking, push alerts, Activity feed, Repairs issues on errors, and Alexa one-off room-clean switches. | `input_select.l10s_vacuum_phase`, `sensor.l10s_vacuum_error`, `repairs.create` | | [vacuum.yaml](vacuum.yaml) | Dreame vacuum orchestration with room tracking, push alerts, Activity feed, Repairs issues on errors, and Alexa one-off room-clean switches. | `input_select.l10s_vacuum_phase`, `sensor.l10s_vacuum_error`, `repairs.create` |
| [hass_agent_homepc.yaml](hass_agent_homepc.yaml) | Mirrors PC lock/unlock state from HASS.Agent to the office lamp for instant desk presence cues. | `sensor.carlo_homepc_carlo_homepc_sessionstate`, `switch.office_lamp_switch` | | [hass_agent_homepc.yaml](hass_agent_homepc.yaml) | Mirrors PC lock/unlock state from HASS.Agent to the office lamp for instant desk presence cues. | `sensor.carlo_homepc_carlo_homepc_sessionstate`, `switch.office_lamp_switch` |
| [sleepiq.yaml](sleepiq.yaml) | Sleep Number presence/snore automations; Overview Health consumes direct SleepIQ integration entities for scores, vitals, pressure, and bed controls. | `sensor.sleepnumber_carlo_carlo_sleep_score`, `sensor.sleepnumber_carlo_stacey_sleep_score`, `number.sleepnumber_carlo_carlo_firmness`, `select.sleepnumber_carlo_foundation_preset_right` | | [sleepiq.yaml](sleepiq.yaml) | Sleep Number presence/snore automations; Overview Health consumes direct SleepIQ integration entities for scores, vitals, pressure, and bed controls. | `sensor.sleepnumber_carlo_carlo_sleep_score`, `sensor.sleepnumber_carlo_stacey_sleep_score`, `number.sleepnumber_carlo_carlo_firmness`, `select.sleepnumber_carlo_foundation_preset_right` |

@ -383,6 +383,15 @@ automation:
action: action:
- variables: - variables:
message: "{{ trigger.json.message | default('Joanna: empty reply') }}" message: "{{ trigger.json.message | default('Joanna: empty reply') }}"
telegram_message: "{{ trigger.json.telegram_message | default(message, true) }}"
telegram_parse_mode: >-
{% set raw = trigger.json.telegram_parse_mode | default('plain_text', true) | string | lower | trim %}
{% if raw in ['html', 'plain_text'] %}
{{ raw }}
{% else %}
plain_text
{% endif %}
telegram_disable_preview: "{{ trigger.json.disable_web_page_preview | default(true, true) }}"
level: "{{ trigger.json.level | default('active') | lower }}" level: "{{ trigger.json.level | default('active') | lower }}"
inline_keyboard_payload: >- inline_keyboard_payload: >-
{% set kb = trigger.json.inline_keyboard if trigger.json.inline_keyboard is defined else none %} {% set kb = trigger.json.inline_keyboard if trigger.json.inline_keyboard is defined else none %}
@ -408,14 +417,16 @@ automation:
- service: telegram_bot.send_message - service: telegram_bot.send_message
data: data:
chat_id: !secret telegram_allowed_chat_id_carlo chat_id: !secret telegram_allowed_chat_id_carlo
message: "{{ message }}" message: "{{ telegram_message }}"
parse_mode: plain_text parse_mode: "{{ telegram_parse_mode }}"
disable_web_page_preview: true disable_web_page_preview: "{{ telegram_disable_preview }}"
inline_keyboard: "{{ inline_keyboard_payload }}" inline_keyboard: "{{ inline_keyboard_payload }}"
default: default:
- service: script.joanna_send_telegram - service: script.joanna_send_telegram
data: data:
message: "{{ message }}" message: "{{ telegram_message }}"
parse_mode: "{{ telegram_parse_mode }}"
disable_web_page_preview: "{{ telegram_disable_preview }}"
- service: script.send_to_logbook - service: script.send_to_logbook
data: data:
topic: JOANNA topic: JOANNA

@ -330,13 +330,8 @@ automation:
trigger: trigger:
- platform: time_pattern - platform: time_pattern
minutes: '/45' minutes: '/30'
- platform: state
entity_id:
- cover.large_garage_door
- cover.small_garage_door
to: 'open'
for: "00:01:00"
- platform: state - platform: state
entity_id: group.family entity_id: group.family
to: not_home to: not_home
@ -392,46 +387,3 @@ automation:
- service: script.speech_engine - service: script.speech_engine
data: data:
value1: "Check the garage doors. The Small garage is {{ states('cover.small_garage_door')}} and the large garage is {{ states('cover.large_garage_door')}} [Always mention the specific garage door that is currently open and remind us to close it for the night]" value1: "Check the garage doors. The Small garage is {{ states('cover.small_garage_door')}} and the large garage is {{ states('cover.large_garage_door')}} [Always mention the specific garage door that is currently open and remind us to close it for the night]"
# - alias: 'Garage Camera on Alexa Shows'
# id: 4373df2a-77f2-4e19-be7c-46c7b27ca583
# mode: single
# trigger:
# - platform: state
# entity_id:
# - cover.large_garage_door
# - cover.small_garage_door
# from: 'closed'
# to: 'open'
# for: "00:00:15"
# - platform: state
# entity_id: binary_sensor.mcu1_gpio12 #interior Garage Doors
# from: 'off'
# to: 'on'
# for: "00:00:05"
# - platform: state
# entity_id:
# - person.carlo
# - person.stacey
# - person.paige
# - person.justin
# to: 'not_home'
# from: 'home'
# action:
# - service: media_player.play_media
# target:
# entity_id: media_player.kitchen
# data:
# media_content_id: 'show garage camera from home assistant'
# media_content_type: custom
# - delay: '00:20:00'
# - service: media_player.play_media
# target:
# entity_id: media_player.kitchen
# data:
# media_content_id: 'hide garage camera'
# media_content_type: custom

@ -7,7 +7,7 @@
# Trigger with input_button.llmvision_garbage_check, input_button.llmvision_front_door_package_check, or front door person activity to update vision-backed package sensors. # Trigger with input_button.llmvision_garbage_check, input_button.llmvision_front_door_package_check, or front door person activity to update vision-backed package sensors.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Notes: LLMVision analyzes camera.garagecam; expects strict "on"/"off" output. # Notes: LLMVision analyzes camera.garagecam; expects strict "on"/"off" output.
# Notes: Front-door package detection runs 3 minutes after `sensor.frontdoorbell_person_active_count_2` goes above 0. # Notes: Front-door package detection waits 3 minutes after person activity or 20 seconds after the front door relocks, then analyzes a short live stream for better package accuracy.
# Docs: https://llmvision.gitbook.io/getting-started/usage/image-analyzer # Docs: https://llmvision.gitbook.io/getting-started/usage/image-analyzer
###################################################################### ######################################################################
@ -129,30 +129,79 @@ automation:
- platform: numeric_state - platform: numeric_state
entity_id: sensor.frontdoorbell_person_active_count_2 entity_id: sensor.frontdoorbell_person_active_count_2
above: 0 above: 0
for: "00:03:00"
id: person_count id: person_count
- platform: state
entity_id: lock.front_door
from: unlocked
to: locked
id: front_door_locked
- platform: state - platform: state
entity_id: input_button.llmvision_front_door_package_check entity_id: input_button.llmvision_front_door_package_check
id: manual_check id: manual_check
variables: variables:
trigger_source: >-
{% if trigger.id == 'manual_check' %}
manual button
{% elif trigger.id == 'front_door_locked' %}
front door relock
{% else %}
front door person activity
{% endif %}
prompt_text: >- prompt_text: >-
Examine the front door camera image for delivery packages. Focus only on boxes, padded envelopes, Examine these front door camera frames for delivery packages. Focus on the porch and doorstep area
or shopping bags left on the porch or by the front door. If one or more packages are clearly visible, near the wall and doormat in the lower-right part of the image. Treat cardboard boxes, padded mailers,
respond exactly: on. If no packages are clearly visible, respond exactly: off. No other words. poly bags, and shopping bags as packages. Ignore the street, cars, landscaping, and anything not resting
on the porch or doorstep. If any package is clearly visible, respond exactly: on. If no package is
clearly visible, respond exactly: off. No other words.
action: action:
- service: llmvision.data_analyzer - choose:
- conditions:
- condition: template
value_template: "{{ trigger.id == 'person_count' }}"
sequence:
- delay: "00:03:00"
- conditions:
- condition: template
value_template: "{{ trigger.id == 'front_door_locked' }}"
sequence:
- delay: "00:00:20"
- service: llmvision.stream_analyzer
response_variable: llmvision_result response_variable: llmvision_result
data: data:
provider: !secret llmvision_provider_entry provider: !secret llmvision_provider_entry
model: gpt-4.1-nano model: gpt-4.1-mini
message: "{{ prompt_text }}" message: "{{ prompt_text }}"
sensor_entity: input_boolean.front_door_packages_present
image_entity: image_entity:
- camera.frontdoorbell - camera.frontdoorbell
duration: 6
max_frames: 5
include_filename: false include_filename: false
target_width: 1280 target_width: 1920
max_tokens: 16 max_tokens: 16
expose_images: true expose_images: true
- variables:
normalized_response: "{{ llmvision_result.response_text | default('') | trim | lower }}"
- choose:
- conditions:
- condition: template
value_template: >-
{{ normalized_response in ['on', 'yes', 'true']
or normalized_response.startswith('on')
or normalized_response.startswith('yes') }}
sequence:
- service: input_boolean.turn_on
target:
entity_id: input_boolean.front_door_packages_present
- conditions:
- condition: template
value_template: >-
{{ normalized_response in ['off', 'no', 'false']
or normalized_response.startswith('off')
or normalized_response.startswith('no') }}
sequence:
- service: input_boolean.turn_off
target:
entity_id: input_boolean.front_door_packages_present
- service: input_text.set_value - service: input_text.set_value
target: target:
entity_id: input_text.llmvision_front_door_last_response entity_id: input_text.llmvision_front_door_last_response
@ -181,7 +230,7 @@ automation:
data: data:
topic: PACKAGES topic: PACKAGES
message: >- message: >-
Front door package vision check ran via {{ 'manual button' if trigger.id == 'manual_check' else 'front door person activity' }} Front door package vision check ran via {{ trigger_source }}
and returned {{ llmvision_result.response_text | default(states('input_boolean.front_door_packages_present')) | lower }}. and returned {{ llmvision_result.response_text | default(states('input_boolean.front_door_packages_present')) | lower }}.
- choose: - choose:
- conditions: - conditions:

@ -7,7 +7,7 @@
# Script wrappers for Telegram messaging using UI-configured integration. # Script wrappers for Telegram messaging using UI-configured integration.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Notes: Do not add `telegram_bot:` YAML here; integration is UI-only. # Notes: Do not add `telegram_bot:` YAML here; integration is UI-only.
# Notes: Joanna transport sends as plain_text to avoid Telegram parse-entity failures. # Notes: Joanna transport defaults to plain_text, but can opt into HTML when the appliance provides a vetted rich message.
# Notes: Keep Skills logic in docker_17/codex_appliance; this package is delivery/transport only. # Notes: Keep Skills logic in docker_17/codex_appliance; this package is delivery/transport only.
###################################################################### ######################################################################
@ -20,9 +20,23 @@ script:
message: message:
description: Message body to send. description: Message body to send.
example: Joanna is online. example: Joanna is online.
parse_mode:
description: Telegram parse mode (`plain_text` or `html`).
example: html
disable_web_page_preview:
description: Whether Telegram should suppress web page previews.
example: true
sequence: sequence:
- variables: - variables:
chunk_size: 3400 chunk_size: 3400
requested_parse_mode: >-
{% set raw = parse_mode | default('plain_text', true) | string | lower | trim %}
{% if raw in ['html', 'plain_text'] %}
{{ raw }}
{% else %}
plain_text
{% endif %}
preview_disabled: "{{ disable_web_page_preview | default(true, true) }}"
normalized_message: >- normalized_message: >-
{% set raw = message | default('', true) | string %} {% set raw = message | default('', true) | string %}
{{ raw | replace('\r\n', '\n') | replace('\r', '\n') | trim }} {{ raw | replace('\r\n', '\n') | replace('\r', '\n') | trim }}
@ -60,8 +74,8 @@ script:
data: data:
chat_id: !secret telegram_allowed_chat_id_carlo chat_id: !secret telegram_allowed_chat_id_carlo
message: "{{ chunk_message }}" message: "{{ chunk_message }}"
parse_mode: plain_text parse_mode: "{{ requested_parse_mode }}"
disable_web_page_preview: true disable_web_page_preview: "{{ preview_disabled }}"
response_variable: telegram_send_response response_variable: telegram_send_response
- choose: - choose:
- conditions: - conditions:
@ -77,4 +91,4 @@ script:
chat_id: !secret telegram_allowed_chat_id_carlo chat_id: !secret telegram_allowed_chat_id_carlo
message: "{{ fallback_message if fallback_message | length > 0 else 'Joanna: message delivery fallback (content omitted)' }}" message: "{{ fallback_message if fallback_message | length > 0 else 'Joanna: message delivery fallback (content omitted)' }}"
parse_mode: plain_text parse_mode: plain_text
disable_web_page_preview: true disable_web_page_preview: "{{ preview_disabled }}"

@ -131,6 +131,12 @@
{%- endif -%} {%- endif -%}
{%- endmacro -%} {%- 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() -%} {%- macro medicine() -%}
{% if is_state('input_boolean.medicine', 'off') -%} {% 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. It looks like Carlo has not taken his medicine yet. Please make sure Carlo takes his medicine now.
@ -293,6 +299,8 @@
{{ window_check() }} {{ window_check() }}
{% endif %} {% endif %}
{{ front_door_packages() }}
{{ NewDevice | default }} {{ NewDevice | default }}
{% if call_garbage_day == 1 %} {% if call_garbage_day == 1 %}

@ -0,0 +1,129 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Open OneNote</title>
<style>
:root { color-scheme: light; }
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: linear-gradient(180deg, #f5f7fb 0%, #e8eef8 100%);
color: #122033;
}
main {
max-width: 32rem;
margin: 0 auto;
min-height: 100vh;
padding: 2rem 1.25rem;
display: flex;
flex-direction: column;
justify-content: center;
gap: 1rem;
}
.card {
background: rgba(255, 255, 255, 0.94);
border: 1px solid rgba(18, 32, 51, 0.08);
border-radius: 18px;
box-shadow: 0 18px 40px rgba(18, 32, 51, 0.12);
padding: 1.25rem;
}
h1 {
margin: 0 0 0.5rem;
font-size: 1.35rem;
line-height: 1.2;
}
p {
margin: 0;
line-height: 1.5;
}
.title {
margin-top: 0.75rem;
font-size: 0.95rem;
color: #41556f;
word-break: break-word;
}
.actions {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1rem;
}
.button {
display: block;
text-decoration: none;
text-align: center;
padding: 0.9rem 1rem;
border-radius: 12px;
font-weight: 600;
}
.button.primary {
background: #166534;
color: #fff;
}
.button.secondary {
background: #fff;
color: #122033;
border: 1px solid rgba(18, 32, 51, 0.14);
}
.hint {
font-size: 0.88rem;
color: #5c708a;
}
</style>
</head>
<body>
<main>
<section class="card">
<h1>Opening OneNote</h1>
<p>Joanna is handing this note to the OneNote app now.</p>
<p id="note-title" class="title"></p>
<div class="actions">
<a id="app-link" class="button primary" href="#">Open in OneNote App</a>
<a id="web-link" class="button secondary" href="https://www.onenote.com/">Open Web Copy</a>
</div>
<p class="hint">If the app does not open automatically in a second or two, tap the OneNote button.</p>
</section>
</main>
<script>
(function () {
var params = new URLSearchParams(window.location.search);
var clientUrl = String(params.get('client') || '').trim();
var webUrl = String(params.get('web') || '').trim() || 'https://www.onenote.com/';
var title = String(params.get('title') || '').trim();
var appLink = document.getElementById('app-link');
var webLink = document.getElementById('web-link');
var titleNode = document.getElementById('note-title');
if (title) {
titleNode.textContent = title;
} else {
titleNode.textContent = 'OneNote note';
}
webLink.href = webUrl;
if (!/^onenote:/i.test(clientUrl)) {
appLink.style.display = 'none';
window.location.replace(webUrl);
return;
}
appLink.href = clientUrl;
var fallbackTimer = window.setTimeout(function () {
window.location.replace(webUrl);
}, 1400);
var cancelFallback = function () {
window.clearTimeout(fallbackTimer);
};
window.addEventListener('pagehide', cancelFallback, { once: true });
document.addEventListener('visibilitychange', function () {
if (document.visibilityState === 'hidden') {
cancelFallback();
}
}, { once: true });
window.location.href = clientUrl;
}());
</script>
</body>
</html>

@ -1,13 +1,13 @@
<svg width="61.5" height="20" viewBox="0 0 615 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="2026.3.2"> <svg width="61.5" height="20" viewBox="0 0 615 200" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="2026.3.2">
<title>2026.3.2</title> <title>2026.3.2</title>
<linearGradient id="SHhAX" x2="0" y2="100%"> <linearGradient id="zwBNs" x2="0" y2="100%">
<stop offset="0" stop-opacity=".1" stop-color="#EEE"/> <stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
<stop offset="1" stop-opacity=".1"/> <stop offset="1" stop-opacity=".1"/>
</linearGradient> </linearGradient>
<mask id="otZCU"><rect width="615" height="200" rx="30" fill="#FFF"/></mask> <mask id="ZDTQc"><rect width="615" height="200" rx="30" fill="#FFF"/></mask>
<g mask="url(#otZCU)"> <g mask="url(#ZDTQc)">
<rect width="615" height="200" fill="#08C" x="0"/> <rect width="615" height="200" fill="#08C" x="0"/>
<rect width="615" height="200" fill="url(#SHhAX)"/> <rect width="615" height="200" fill="url(#zwBNs)"/>
</g> </g>
<g aria-hidden="true" fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110"> <g aria-hidden="true" fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110">
<text x="65" y="148" textLength="500" fill="#000" opacity="0.25">2026.3.2</text> <text x="65" y="148" textLength="500" fill="#000" opacity="0.25">2026.3.2</text>

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 811 B

Loading…
Cancel
Save

Powered by TurnKey Linux.