Merge remote-tracking branch 'origin/master' into feature/powerwall-live-activity-1598

# Conflicts:
#	.gitignore
#	config/packages/README.md
pull/1610/head
Carlo Costanzo 2 months ago
commit b0cd52d11d

1
.gitignore vendored

@ -81,3 +81,4 @@ panel-notes
docker_14 docker_14
docker_69 docker_69
.codex_tmp/ .codex_tmp/

@ -1 +1 @@
2026.3.2 2026.4.1

@ -194,11 +194,13 @@
card_param: cards card_param: cards
filter: filter:
include: include:
- group: group.docker_monitored_containers - entity_id: "/^switch\\..*_container(_2)?$/"
options: options:
type: custom:button-card type: custom:button-card
template: bearstone_infra_container_row template: bearstone_infra_container_row
icon: mdi:docker icon: mdi:docker
exclude:
- state: unavailable
- state: unknown
sort: sort:
method: name method: name

@ -449,7 +449,7 @@
card_param: cards card_param: cards
filter: filter:
include: include:
- group: group.docker_monitored_containers - entity_id: "/^switch\\..*_container(_2)?$/"
options: options:
type: custom:button-card type: custom:button-card
template: bearstone_infra_container_row template: bearstone_infra_container_row
@ -636,4 +636,3 @@
name: Download name: Download
- entity: sensor.speedtest_upload - entity: sensor.speedtest_upload
name: Upload name: Upload

@ -614,13 +614,15 @@ bearstone_infra_container_row:
} }
const switchEntity = key ? `switch.${key}_container` : ''; const switchEntity = key ? `switch.${key}_container` : '';
const switchEntityAlt = key ? `switch.${key}_container_2` : ''; const switchEntityAlt = key ? `switch.${key}_container_2` : '';
const monitored = states['group.docker_monitored_containers']?.attributes?.entity_id || []; const monitored = states['sensor.docker_monitored_switch_inventory']?.attributes?.entity_id;
const restartCandidates = key ? [ const restartCandidates = key ? [
`button.${key}_restart_container`, `button.${key}_restart_container`,
`button.${key}_restart_container_2`, `button.${key}_restart_container_2`,
] : []; ] : [];
const hasRestart = restartCandidates.some((candidate) => states[candidate]); const hasRestart = restartCandidates.some((candidate) => states[candidate]);
const isMonitored = monitored.includes(switchEntity) || monitored.includes(switchEntityAlt); const isMonitored = Array.isArray(monitored)
? monitored.includes(switchEntity) || monitored.includes(switchEntityAlt)
: (ent.startsWith('switch.') && (ent.endsWith('_container') || ent.endsWith('_container_2')));
return (hasRestart && isMonitored) ? 'block' : 'none'; return (hasRestart && isMonitored) ? 'block' : 'none';
]]] ]]]

@ -1,3 +0,0 @@
# Sensors:
# entities:
# # - binary_sensor.aeotec_dsb04100_doorwindow_sensor_sensor_3_0

@ -53,13 +53,14 @@ Live collection of plug-and-play Home Assistant packages. Each YAML file in this
| [mqtt_status.yaml](mqtt_status.yaml) | Command-line MQTT broker reachability probe with Spook Repairs escalation and Joanna troubleshooting dispatch on outage. | `binary_sensor.mqtt_status_raw`, `binary_sensor.mqtt_broker_problem`, `repairs.create`, `rest_command.bearclaw_command` | | [mqtt_status.yaml](mqtt_status.yaml) | Command-line MQTT broker reachability probe with Spook Repairs escalation and Joanna troubleshooting dispatch on outage. | `binary_sensor.mqtt_status_raw`, `binary_sensor.mqtt_broker_problem`, `repairs.create`, `rest_command.bearclaw_command` |
| [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` | | [mariadb.yaml](mariadb.yaml) | MariaDB recorder health and capacity SQL sensors. | `sensor.mariadb_status`, `sensor.database_size` |
| [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts, plus event-based Joanna dispatch when reports include `### Available:` (24h cooldown via `mode: single` + delay, no new helpers). | `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` | | [tugtainer_updates.yaml](tugtainer_updates.yaml) | Tugtainer container update notifications via webhook + persistent alerts, plus event-based Joanna dispatch when reports include `### Available:` (24h cooldown via `mode: single` + delay, no new helpers). | `persistent_notification.create`, `event: tugtainer_available_detected`, `script.joanna_dispatch`, `input_datetime.tugtainer_last_update` |
| [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch KPI sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `automation.bearclaw_*`, `script.send_to_logbook` | | [bearclaw.yaml](bearclaw.yaml) | Joanna/BearClaw bridge automations that forward Telegram commands to codex_appliance, include LLM-first routing context for freeform text, relay replies back, ingest `/api/bearclaw/status` telemetry, and expose dispatch KPI sensors for Infrastructure dashboards. | `rest_command.bearclaw_*`, `sensor.bearclaw_status_telemetry`, `sensor.joanna_*`, `automation.bearclaw_*`, `script.send_to_logbook` |
| [telegram_bot.yaml](telegram_bot.yaml) | Telegram script wrappers used by BearClaw and other ops flows (UI integration remains the source for bot config). | `script.joanna_send_telegram`, `telegram_bot.send_message` | | [telegram_bot.yaml](telegram_bot.yaml) | Telegram script wrappers used by BearClaw and other ops flows (UI config remains source of truth; wrappers work with polling or webhook transport). | `script.joanna_send_telegram`, `telegram_bot.send_message` |
| [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` | | [phynplus.yaml](phynplus.yaml) | Phyn shutoff automations with push + Activity feed + Repairs issues for leak events. | `valve.phyn_shutoff_valve`, `binary_sensor.phyn_leak_test_running`, `repairs.create` |
| [water_delivery.yaml](water_delivery.yaml) | ReadyRefresh delivery date helper with night-before + garage door Alexa reminders, plus helper-change audit logging and Telegram confirmations. | `input_datetime.water_delivery_date`, `script.send_to_logbook`, `script.joanna_send_telegram`, `notify.alexa_media_garage` | | [water_delivery.yaml](water_delivery.yaml) | ReadyRefresh delivery date helper with night-before + garage door Alexa reminders, plus helper-change audit logging and Telegram confirmations. | `input_datetime.water_delivery_date`, `script.send_to_logbook`, `script.joanna_send_telegram`, `notify.alexa_media_garage` |
| [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, push live outage tracking to mobile targets, and shed loads automatically when off-grid (alerts include Activity feed + Repairs). | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `script.notify_live_activity`, `repairs.create` | | [powerwall.yaml](powerwall.yaml) | Track Tesla Powerwall grid status, push live outage tracking to mobile targets, and shed loads automatically when off-grid (alerts include Activity feed + Repairs). | `binary_sensor.powerwall_grid_status`, `sensor.powerwall_*`, `script.notify_live_activity`, `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` |

@ -38,7 +38,7 @@ automation:
trigger: trigger:
- platform: numeric_state - platform: numeric_state
entity_id: sensor.front_door_battery entity_id: sensor.front_door_battery
below: 25 below: 20
action: action:
- service: script.notify_engine - service: script.notify_engine
@ -51,24 +51,3 @@ automation:
topic: "BATTERY" topic: "BATTERY"
message: "August Door lock battery low: {{ states('sensor.front_door_battery') }}%" message: "August Door lock battery low: {{ states('sensor.front_door_battery') }}%"
# - alias: 'FrontDoor Bell Camera on Front Door Lock'
# id: Doorbell_camera_front_door
# mode: single
# trigger:
# - platform: state
# entity_id: lock.front_door
# to: 'unlocked'
# action:
# - service: media_player.play_media
# target:
# entity_id: media_player.kitchen
# data:
# media_content_id: 'show front doorbell camera'
# media_content_type: custom
# - delay: '00:20:00'
# - service: media_player.play_media
# target:
# entity_id: media_player.kitchen
# data:
# media_content_id: 'hide front doorbell camera'
# media_content_type: custom

@ -14,6 +14,8 @@
# Notes: Inbound Telegram handling enforces user_id + chat_id allowlists from secrets CSV values. # Notes: Inbound Telegram handling enforces user_id + chat_id allowlists from secrets CSV values.
# Notes: Reply webhook writes JOANNA activity entries to logbook for traceability. # Notes: Reply webhook writes JOANNA activity entries to logbook for traceability.
# Notes: Status telemetry polling expects !secret bearclaw_status_url (token header stays !secret bearclaw_token). # Notes: Status telemetry polling expects !secret bearclaw_status_url (token header stays !secret bearclaw_token).
# Notes: Telegram freeform input now includes LLM-first routing context to improve intent understanding before entity lookups.
# Notes: Command payload supports async_only for automation-first queueing when immediate inline handling is not required.
# Notes: Blog: https://www.vcloudinfo.com/2026/03/joanna-dispatch-telemetry-home-assistant-infrastructure-dashboard/ # Notes: Blog: https://www.vcloudinfo.com/2026/03/joanna-dispatch-telemetry-home-assistant-infrastructure-dashboard/
###################################################################### ######################################################################
@ -21,6 +23,7 @@ rest_command:
bearclaw_command: bearclaw_command:
url: !secret bearclaw_command_url url: !secret bearclaw_command_url
method: post method: post
timeout: 30
content_type: application/json content_type: application/json
headers: headers:
x-codex-token: !secret bearclaw_token x-codex-token: !secret bearclaw_token
@ -30,7 +33,8 @@ rest_command:
"user": {{ user | default('carlo') | tojson }}, "user": {{ user | default('carlo') | tojson }},
"source": {{ source | default('home_assistant') | tojson }}, "source": {{ source | default('home_assistant') | tojson }},
"context": {{ context | default(none) | tojson }}, "context": {{ context | default(none) | tojson }},
"callback": {{ callback | default(none) | tojson }} "callback": {{ callback | default(none) | tojson }},
"async_only": {{ async_only | default(false) | tojson }}
} }
bearclaw_ingest: bearclaw_ingest:
@ -341,7 +345,7 @@ automation:
- id: bearclaw_telegram_text_no_slash_needed - id: bearclaw_telegram_text_no_slash_needed
alias: BearClaw Telegram Text No Slash Needed alias: BearClaw Telegram Text No Slash Needed
description: Treats plain Telegram text as BearClaw command input. description: Treats plain Telegram text as BearClaw command input and forwards LLM-first routing context.
mode: queued mode: queued
trigger: trigger:
- platform: event - platform: event
@ -363,12 +367,36 @@ automation:
action: action:
- variables: - variables:
plain_text: "{{ trigger.event.data.text | default('') | trim }}" plain_text: "{{ trigger.event.data.text | default('') | trim }}"
plain_text_lower: "{{ plain_text | lower }}"
from_user: "{{ (trigger.event.data.from_first | default('carlo')) | lower }}" from_user: "{{ (trigger.event.data.from_first | default('carlo')) | lower }}"
command_like_request: >-
{% set action_verbs = ['disable', 'enable', 'turn off', 'turn on', 'stop', 'start', 'restart', 'review', 'fix', 'change', 'update', 'set', 'open', 'close'] %}
{{ action_verbs | select('in', plain_text_lower) | list | count > 0 }}
status_like_request: >-
{{ (plain_text_lower.startswith('is ')
or plain_text_lower.startswith('are ')
or plain_text_lower.startswith('what is ')
or plain_text_lower.startswith("what's ")
or ' status' in plain_text_lower
or plain_text_lower.startswith('status '))
and not command_like_request }}
llm_route_hint: >-
{% if command_like_request %}
llm_first_action
{% elif status_like_request %}
llm_first_status
{% else %}
llm_first_general
{% endif %}
llm_context: >-
telegram_freeform route={{ llm_route_hint | trim }}.
Prefer LLM intent interpretation and clarification for action or automation requests before returning entity status.
- service: rest_command.bearclaw_command - service: rest_command.bearclaw_command
data: data:
text: "{{ plain_text }}" text: "{{ plain_text }}"
user: "{{ from_user }}" user: "{{ from_user }}"
source: telegram_text source: telegram_text
context: "{{ llm_context | trim }}"
- id: bearclaw_reply_webhook - id: bearclaw_reply_webhook
alias: BearClaw Reply Webhook alias: BearClaw Reply Webhook
@ -383,6 +411,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 +445,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

@ -4,13 +4,14 @@
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Docker Infrastructure - Host patching and container alerts # Docker Infrastructure - Host patching and container alerts
# Related Issue: 1632, 1584
# APT webhook results (docker_10/14/17/69) and container down repairs. # APT webhook results (docker_10/14/17/69) and container down repairs.
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Related Issue: 1584
# Notes: Hosts run weekly Wed 12:00 APT job and POST JSON to webhooks. # Notes: Hosts run weekly Wed 12:00 APT job and POST JSON to webhooks.
# Notes: Reboots are handled directly on each host by apt_weekly.sh. # Notes: Reboots are handled directly on each host by apt_weekly.sh.
# Notes: Reboot staggering: docker_14 first, docker_69 second, docker_10 third. # Notes: Reboot staggering: docker_14 first, docker_69 second, docker_10 third.
# Notes: Container monitoring is dynamic with binary_sensor status preferred over switch state. # Notes: Container monitoring is dynamic with binary_sensor status preferred over switch state.
# Notes: Weekly Joanna reconcile checks discovered container switches vs configured group members.
# Notes: Includes Portainer stack status repairs, 20-minute Joanna dispatch for persistent container outages, and scheduled image prune. # Notes: Includes Portainer stack status repairs, 20-minute Joanna dispatch for persistent container outages, and scheduled image prune.
###################################################################### ######################################################################
@ -66,57 +67,120 @@ input_text:
name: "docker_69 APT last result" name: "docker_69 APT last result"
max: 255 max: 255
group: switch:
docker_monitored_containers: - platform: group
name: Docker Monitored Containers name: Docker Monitored Containers
unique_id: docker_monitored_containers
entities: entities:
- switch.3a446393a8c5_dozzle_container
- switch.5b3503372936_wordpress_db_container
- switch.7a83770eb321_rc_price_checker_container
- switch.91aca40203c0_portainer_agent_container
- switch.cloudflared_kch_container - switch.cloudflared_kch_container
- switch.cloudflared_kch_container_2
- switch.cloudflared_wp_container - switch.cloudflared_wp_container
- switch.cloudflared_wp_container_2
- switch.codex_appliance_container - switch.codex_appliance_container
- switch.codex_appliance_container_2
- switch.college_budget_app_container - switch.college_budget_app_container
- switch.college_budget_app_container_2
- switch.cruise_tracker_container - switch.cruise_tracker_container
- switch.cruise_tracker_container_2
- switch.dashy_container - switch.dashy_container
- switch.dashy_container_2
- switch.docker_socket_proxy_container - switch.docker_socket_proxy_container
- switch.dozzle_container - switch.docker_socket_proxy_container_2
- switch.dozzle_agent_10_container - switch.dozzle_agent_10_container
- switch.dozzle_agent_10_container_2
- switch.dozzle_agent_14_container - switch.dozzle_agent_14_container
- switch.dozzle_agent_14_container_2
- switch.dozzle_agent_17_container - switch.dozzle_agent_17_container
- switch.dozzle_agent_17_container_2
- switch.dozzle_agent_69_container - switch.dozzle_agent_69_container
- switch.dozzle_agent_69_container_2
- switch.dozzle_container
- switch.dozzle_container_2
- switch.duplicati_container - switch.duplicati_container
- switch.duplicati_container_2
- switch.esphome_container - switch.esphome_container
- switch.esphome_container_2
- switch.fed437a0f191_tugtainer_socket_proxy_container - switch.fed437a0f191_tugtainer_socket_proxy_container
- switch.foodie_tracker_container - switch.foodie_tracker_container
- switch.foodie_tracker_container_2
- switch.frigate_container - switch.frigate_container
- switch.frigate_container_2
- switch.games_hub_container - switch.games_hub_container
- switch.games_hub_container_2
- switch.home_assistant_container - switch.home_assistant_container
- switch.home_assistant_container_2
- switch.imposter_container - switch.imposter_container
- switch.imposter_container_2
- switch.infra_info_container - switch.infra_info_container
- switch.infra_info_container_2
- switch.kingcrafthomes_container - switch.kingcrafthomes_container
- switch.kingcrafthomes_container_2
- switch.lmediaservices_container - switch.lmediaservices_container
- switch.mariadb_container - switch.lmediaservices_container_2
- switch.mariadb_backup_container - switch.mariadb_backup_container
- switch.mariadb_backup_container_2
- switch.mariadb_container
- switch.mariadb_container_2
- switch.matter_server_container - switch.matter_server_container
- switch.matter_server_container_2
- switch.mqtt_container - switch.mqtt_container
- switch.mqtt_container_2
- switch.nebula_sync_container - switch.nebula_sync_container
- switch.nebula_sync_container_2
- switch.onenote_indexer_container
- switch.onenote_indexer_container_2
- switch.optimistic_mclaren_container
- switch.panel_notes_container - switch.panel_notes_container
- switch.panel_notes_container_2
- switch.pihole_container - switch.pihole_container
- switch.pihole_container_2
- switch.pihole_secondary_container - switch.pihole_secondary_container
- switch.pihole_secondary_container_2
- switch.poker_tracker_container - switch.poker_tracker_container
- switch.portainer_container - switch.poker_tracker_container_2
- switch.portainer_agent_69_container
- switch.portainer_agent_container - switch.portainer_agent_container
- switch.portainer_agent_container_2
- switch.portainer_container
- switch.portainer_container_2
- switch.postgres_webhooks_engine_container - switch.postgres_webhooks_engine_container
- switch.postgres_webhooks_engine_container_2
- switch.rc_price_checker_container - switch.rc_price_checker_container
- switch.rc_price_checker_container_2
- switch.redis_webhooks_engine_container - switch.redis_webhooks_engine_container
- switch.redis_webhooks_engine_container_2
- switch.rvtools_ppt_web_container - switch.rvtools_ppt_web_container
- switch.rvtools_ppt_web_container_2
- switch.steelesharing_home_container
- switch.tapple_container - switch.tapple_container
- switch.tugtainer_container - switch.tapple_container_2
- switch.tugtainer_agent_14_container
- switch.tugtainer_agent_17_container
- switch.tugtainer_agent_69_container
- switch.tugtainer_agent_container - switch.tugtainer_agent_container
- switch.tugtainer_agent_container_2
- switch.tugtainer_container
- switch.tugtainer_container_2
- switch.tugtainer_socket_proxy_14_container
- switch.tugtainer_socket_proxy_17_container
- switch.tugtainer_socket_proxy_69_container
- switch.tugtainer_socket_proxy_container - switch.tugtainer_socket_proxy_container
- switch.tugtainer_socket_proxy_container_2
- switch.unifi_container - switch.unifi_container
- switch.unifi_container_2
- switch.webhooks_engine_container - switch.webhooks_engine_container
- switch.webhooks_engine_container_2
- switch.wordpress_db_container - switch.wordpress_db_container
- switch.wordpress_db_container_2
- switch.wordpress_wp_container - switch.wordpress_wp_container
- switch.wordpress_wp_container_2
- switch.wyze_bridge_container - switch.wyze_bridge_container
- switch.wyze_bridge_container_2
template: template:
- sensor: - sensor:
@ -202,18 +266,133 @@ template:
{% endif %} {% endif %}
- sensor: - sensor:
- name: "Docker Monitored Switch Inventory"
unique_id: docker_monitored_switch_inventory
icon: mdi:docker
state: >-
{% set ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = (expand('binary_sensor.' ~ key ~ '_status') | count > 0
and states('binary_sensor.' ~ key ~ '_status') | lower not in ['unknown', 'unavailable', ''])
or (expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
and states('binary_sensor.' ~ key ~ '_status_2') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state') | count > 0
and states('sensor.' ~ key ~ '_state') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state_2') | count > 0
and states('sensor.' ~ key ~ '_state_2') | lower not in ['unknown', 'unavailable', '']) %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in ns.items %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.items | sort | count }}
attributes:
entity_id: >-
{% set ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = (expand('binary_sensor.' ~ key ~ '_status') | count > 0
and states('binary_sensor.' ~ key ~ '_status') | lower not in ['unknown', 'unavailable', ''])
or (expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
and states('binary_sensor.' ~ key ~ '_status_2') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state') | count > 0
and states('sensor.' ~ key ~ '_state') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state_2') | count > 0
and states('sensor.' ~ key ~ '_state_2') | lower not in ['unknown', 'unavailable', '']) %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in ns.items %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
configured_group_members: >-
{{ state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list | sort }}
missing_from_group: >-
{% set discovered_ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = (expand('binary_sensor.' ~ key ~ '_status') | count > 0
and states('binary_sensor.' ~ key ~ '_status') | lower not in ['unknown', 'unavailable', ''])
or (expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
and states('binary_sensor.' ~ key ~ '_status_2') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state') | count > 0
and states('sensor.' ~ key ~ '_state') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state_2') | count > 0
and states('sensor.' ~ key ~ '_state_2') | lower not in ['unknown', 'unavailable', '']) %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in discovered_ns.items %}
{% set discovered_ns.items = discovered_ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% set discovered = discovered_ns.items | list %}
{% set configured = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set ns = namespace(items=[]) %}
{% for ent in discovered %}
{% if ent not in configured %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
stale_group_members: >-
{% set discovered_ns = namespace(items=[]) %}
{% for item in states.switch %}
{% set ent = item.entity_id %}
{% if ent is search('^switch\\..*_container(?:_2)?$') %}
{% set key = ent | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% set has_companion = (expand('binary_sensor.' ~ key ~ '_status') | count > 0
and states('binary_sensor.' ~ key ~ '_status') | lower not in ['unknown', 'unavailable', ''])
or (expand('binary_sensor.' ~ key ~ '_status_2') | count > 0
and states('binary_sensor.' ~ key ~ '_status_2') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state') | count > 0
and states('sensor.' ~ key ~ '_state') | lower not in ['unknown', 'unavailable', ''])
or (expand('sensor.' ~ key ~ '_state_2') | count > 0
and states('sensor.' ~ key ~ '_state_2') | lower not in ['unknown', 'unavailable', '']) %}
{% set switch_state = states(ent) | lower %}
{% if has_companion or switch_state not in ['unknown', 'unavailable', ''] %}
{% if ent not in discovered_ns.items %}
{% set discovered_ns.items = discovered_ns.items + [ent] %}
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
{% set discovered = discovered_ns.items | list %}
{% set configured = state_attr('switch.docker_monitored_containers', 'entity_id') | default([], true) | list %}
{% set ns = namespace(items=[]) %}
{% for ent in configured %}
{% if ent not in discovered %}
{% set ns.items = ns.items + [ent] %}
{% endif %}
{% endfor %}
{{ ns.items | sort }}
- name: "Docker Monitored Container Count" - name: "Docker Monitored Container Count"
unique_id: docker_monitored_container_count unique_id: docker_monitored_container_count
icon: mdi:format-list-numbered icon: mdi:format-list-numbered
state: >- state: >-
{{ state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | count }} {{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | count }}
- name: "Docker Monitored Unavailable Count" - name: "Docker Monitored Unavailable Count"
unique_id: docker_monitored_unavailable_count unique_id: docker_monitored_unavailable_count
icon: mdi:lan-disconnect icon: mdi:lan-disconnect
state: >- state: >-
{% set ns = namespace(keys=[], unavailable=0) %} {% set ns = namespace(keys=[], unavailable=0) %}
{% set monitored = state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) %} {% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) %}
{% for switch_entity in monitored %} {% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %} {% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
{% if key not in ns.keys %} {% if key not in ns.keys %}
@ -257,7 +436,7 @@ template:
icon: mdi:docker icon: mdi:docker
state: >- state: >-
{% set ns = namespace(keys=[], down=[]) %} {% set ns = namespace(keys=[], down=[]) %}
{% set monitored = state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | list %} {% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list %}
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %} {% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
{% for switch_entity in monitored %} {% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %} {% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
@ -301,7 +480,7 @@ template:
attributes: attributes:
down_containers: >- down_containers: >-
{% set ns = namespace(keys=[], down=[]) %} {% set ns = namespace(keys=[], down=[]) %}
{% set monitored = state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | list %} {% set monitored = state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list %}
{% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %} {% set telemetry_degraded = is_state('binary_sensor.docker_container_telemetry_degraded', 'on') %}
{% for switch_entity in monitored %} {% for switch_entity in monitored %}
{% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %} {% set key = switch_entity | replace('switch.', '') | regex_replace('_container(?:_2)?$', '') %}
@ -437,7 +616,7 @@ script:
status_entity_alt: "binary_sensor.{{ container_key }}_status_2" status_entity_alt: "binary_sensor.{{ container_key }}_status_2"
state_entity: "sensor.{{ container_key }}_state" state_entity: "sensor.{{ container_key }}_state"
state_entity_alt: "sensor.{{ container_key }}_state_2" state_entity_alt: "sensor.{{ container_key }}_state_2"
monitored_switches: "{{ state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) }}" monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) }}"
tracked_container: "{{ switch_entity in monitored_switches or switch_entity_alt in monitored_switches }}" tracked_container: "{{ switch_entity in monitored_switches or switch_entity_alt in monitored_switches }}"
effective_entity: >- effective_entity: >-
{% if expand(status_entity) | count > 0 %} {% if expand(status_entity) | count > 0 %}
@ -567,7 +746,13 @@ script:
effective_state_20m={{ persistent_effective_state }} effective_state_20m={{ persistent_effective_state }}
request: >- request: >-
Troubleshoot and resolve the persistent Docker container outage if possible. Troubleshoot and resolve the persistent Docker container outage if possible.
Use Duplicati and the related host/container telemetry to verify recovery. Reply with explicit status fields:
resolved=true/false,
root_cause,
action_taken,
verification (entity plus observed state),
next_action_required=true/false.
Use Duplicati and related host/container telemetry to verify recovery.
- conditions: "{{ op == 'clear' }}" - conditions: "{{ op == 'clear' }}"
sequence: sequence:
- variables: - variables:
@ -798,7 +983,7 @@ automation:
entity_id: "{{ trigger.event.data.entity_id | default('') }}" entity_id: "{{ trigger.event.data.entity_id | default('') }}"
old_state: "{{ (trigger.event.data.old_state.state if trigger.event.data.old_state is not none else '') | lower }}" old_state: "{{ (trigger.event.data.old_state.state if trigger.event.data.old_state is not none else '') | lower }}"
new_state: "{{ (trigger.event.data.new_state.state if trigger.event.data.new_state is not none else '') | lower }}" new_state: "{{ (trigger.event.data.new_state.state if trigger.event.data.new_state is not none else '') | lower }}"
monitored_switches: "{{ state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | list }}" monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
is_monitored_container_event: >- is_monitored_container_event: >-
{% set ent = entity_id %} {% set ent = entity_id %}
{% if ent.startswith('switch.') and (ent.endswith('_container') or ent.endswith('_container_2')) %} {% if ent.startswith('switch.') and (ent.endswith('_container') or ent.endswith('_container_2')) %}
@ -888,7 +1073,7 @@ automation:
minutes: "/55" minutes: "/55"
action: action:
- variables: - variables:
monitored_switches: "{{ state_attr('group.docker_monitored_containers', 'entity_id') | default([], true) | list }}" monitored_switches: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
- repeat: - repeat:
for_each: "{{ monitored_switches }}" for_each: "{{ monitored_switches }}"
sequence: sequence:
@ -1009,11 +1194,64 @@ automation:
- service: homeassistant.update_entity - service: homeassistant.update_entity
target: target:
entity_id: entity_id:
- sensor.docker_monitored_switch_inventory
- sensor.docker_monitored_unavailable_count - sensor.docker_monitored_unavailable_count
- sensor.docker_containers_down_list - sensor.docker_containers_down_list
- sensor.docker_containers_down_count - sensor.docker_containers_down_count
- binary_sensor.docker_container_telemetry_degraded - binary_sensor.docker_container_telemetry_degraded
- alias: "Docker Group Reconcile - Weekly Joanna Review"
id: docker_group_reconcile_weekly_joanna_review
description: "Weekly reconciliation of discovered Docker container entities vs configured group members."
mode: single
trigger:
- platform: time
at: "08:45:00"
condition:
- condition: time
weekday:
- sun
action:
- variables:
discovered_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'entity_id') | default([], true) | list }}"
configured_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'configured_group_members') | default([], true) | list }}"
missing_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'missing_from_group') | default([], true) | list }}"
stale_members: "{{ state_attr('sensor.docker_monitored_switch_inventory', 'stale_group_members') | default([], true) | list }}"
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Weekly reconcile check: discovered={{ discovered_members | count }},
configured={{ configured_members | count }},
missing={{ missing_members | count }},
stale={{ stale_members | count }}.
- choose:
- conditions:
- condition: template
value_template: "{{ (missing_members | count > 0) or (stale_members | count > 0) }}"
sequence:
- service: script.joanna_dispatch
data:
trigger_context: >-
HA automation docker_group_reconcile_weekly_joanna_review
(Docker Group Reconcile - Weekly Joanna Review)
source: "home_assistant_automation.docker_group_reconcile_weekly_joanna_review"
summary: >-
Docker group membership drift detected (missing={{ missing_members | count }},
stale={{ stale_members | count }}).
entity_ids:
- sensor.docker_monitored_switch_inventory
- switch.docker_monitored_containers
diagnostics: >-
discovered={{ discovered_members | join(', ') if (discovered_members | count > 0) else 'none' }};
configured={{ configured_members | join(', ') if (configured_members | count > 0) else 'none' }};
missing={{ missing_members | join(', ') if (missing_members | count > 0) else 'none' }};
stale={{ stale_members | join(', ') if (stale_members | count > 0) else 'none' }}.
request: >-
Reconcile Docker monitored group members in
config/packages/docker_infrastructure.yaml so configured monitoring
matches currently discovered container entities.
- alias: "Docker Weekly Prune Unused Images" - alias: "Docker Weekly Prune Unused Images"
id: docker_weekly_prune_unused_images id: docker_weekly_prune_unused_images
description: "Run weekly unguarded prune actions across Docker hosts." description: "Run weekly unguarded prune actions across Docker hosts."

@ -16,6 +16,7 @@
mqtt: mqtt:
cover: cover:
- name: "Large Garage Door" - name: "Large Garage Door"
unique_id: garadget_large_garage_door_cover
device_class: 'garage' device_class: 'garage'
command_topic: "garadget/GLarge/command" command_topic: "garadget/GLarge/command"
state_topic: "garadget/GLarge/status" state_topic: "garadget/GLarge/status"
@ -30,6 +31,7 @@ mqtt:
payload_stop: "stop" payload_stop: "stop"
- name: "Small Garage Door" - name: "Small Garage Door"
unique_id: garadget_small_garage_door_cover
device_class: 'garage' device_class: 'garage'
command_topic: "garadget/GSmall/command" command_topic: "garadget/GSmall/command"
state_topic: "garadget/GSmall/status" state_topic: "garadget/GSmall/status"
@ -45,19 +47,23 @@ mqtt:
sensor: sensor:
- name: "Large Garage Door Since" - name: "Large Garage Door Since"
unique_id: garadget_large_garage_door_since
state_topic: "garadget/GLarge/status" state_topic: "garadget/GLarge/status"
value_template: '{{ value_json.time }}' value_template: '{{ value_json.time }}'
- name: "Large Garage Door Brightness" - name: "Large Garage Door Brightness"
unique_id: garadget_large_garage_door_brightness
state_topic: "garadget/GLarge/status" state_topic: "garadget/GLarge/status"
unit_of_measurement: '%' unit_of_measurement: '%'
value_template: '{{ value_json.bright }}' value_template: '{{ value_json.bright }}'
- name: "Small Garage Door Since" - name: "Small Garage Door Since"
unique_id: garadget_small_garage_door_since
state_topic: "garadget/GSmall/status" state_topic: "garadget/GSmall/status"
value_template: '{{ value_json.time }}' value_template: '{{ value_json.time }}'
- name: "Small Garage Door Brightness" - name: "Small Garage Door Brightness"
unique_id: garadget_small_garage_door_brightness
state_topic: "garadget/GSmall/status" state_topic: "garadget/GSmall/status"
unit_of_measurement: '%' unit_of_measurement: '%'
value_template: '{{ value_json.bright }}' value_template: '{{ value_json.bright }}'
@ -241,43 +247,6 @@ automation:
topic: "garadget/GLarge/command" topic: "garadget/GLarge/command"
payload: "get-status" payload: "get-status"
- alias: 'Garage Door State Change'
id: afec0987-edb1-4341-a524-a00ae4df9fb7
mode: restart
trigger:
- platform: state
entity_id:
- cover.large_garage_door
- cover.small_garage_door
from: 'open'
to: 'closed'
for: '00:02:00'
- platform: state
entity_id:
- cover.large_garage_door
- cover.small_garage_door
from: 'closed'
to: 'open'
for: '00:10:00'
action:
- choose:
- conditions: "{{ is_state('cover.large_garage_door','closed') }}"
sequence:
- service: script.speech_engine
data:
DoorClosed: "The {{ trigger.entity_id.split('.')[1]|replace('_', ' ') }} is now {{ (trigger.to_state.state)|replace('_', ' ') }}."
call_garage_check: 1
- delay: "00:10:00"
default:
- service: script.speech_engine
data:
DoorClosed: "The {{ trigger.entity_id.split('.')[1]|replace('_', ' ') }} is now {{ (trigger.to_state.state)|replace('_', ' ') }}."
call_garage_check: 1
################################### ###################################
## Garadget Wind Door Checks - [Garadget](https://amzn.to/2jQLpVQ) ## Garadget Wind Door Checks - [Garadget](https://amzn.to/2jQLpVQ)
################################### ###################################
@ -330,13 +299,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 +356,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

@ -0,0 +1,45 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# GitHub Watched Repo Scout - Nightly Joanna review of watched repos
# Schedules Joanna to review unread watched-repo notifications for HA ideas.
# -------------------------------------------------------------------
# Notes: Joanna native GitHub scout logic runs in docker_17/codex_appliance.
# Notes: Successfully processed watched-repo notifications are marked read by Joanna.
######################################################################
automation:
- id: github_watched_repo_scout_nightly
alias: GitHub Watched Repo Scout - Nightly Joanna Review
description: Ask Joanna nightly to review unread watched-repo GitHub notifications for Home Assistant ideas.
mode: single
trigger:
- platform: time
at: "03:15:00"
variables:
trigger_context: "HA automation github_watched_repo_scout_nightly (GitHub Watched Repo Scout - Nightly Joanna Review)"
action:
- service: script.send_to_logbook
data:
topic: "GITHUB"
message: "Requesting Joanna nightly watched-repo scout review."
- service: script.joanna_dispatch
data:
trigger_context: "{{ trigger_context }}"
source: "home_assistant_automation.github_watched_repo_scout_nightly"
summary: "Nightly GitHub watched-repo scout review for Home Assistant ideas"
diagnostics: >-
schedule=03:15:00,
inbox_scope=unread,
repo_scope=watched,
create_issues=true,
mark_read_on_success=true
request: >-
Review unread GitHub notifications from watched repositories only.
Look for interesting Home Assistant configuration ideas we could apply in
our own repo, send a concise nightly recap, create or refresh one
GitHub issue per strong candidate in CCOSTAN/Home-AssistantConfig, and
mark each watched-repo notification read only after it has been
processed successfully. Leave failed items unread for the next run.

@ -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:

@ -1,12 +1,13 @@
#-------------------------------------------
# @CCOSTAN
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# Office Motion - Motion/illuminance triggers for office lighting.
#-------------------------------------------
###################################################################### ######################################################################
## Office motion sensors and automations. # @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Office Motion - MQTT motion sensor and office lighting automations
# Office occupancy trigger that drives office light on/off routines.
# -------------------------------------------------------------------
# Notes: Light sensor node hardware reference https://amzn.to/2oUgj5i
###################################################################### ######################################################################
# Light sensor node: https://amzn.to/2oUgj5i
homeassistant: homeassistant:
customize: customize:
binary_sensor.office_motion: binary_sensor.office_motion:
@ -18,6 +19,7 @@ mqtt:
binary_sensor: binary_sensor:
- state_topic: "NodeMCU4/Motion/Motion" - state_topic: "NodeMCU4/Motion/Motion"
name: "Office_Motion" name: "Office_Motion"
unique_id: office_motion_sensor
payload_on: 1 payload_on: 1
payload_off: 0 payload_off: 0
device_class: motion device_class: motion

@ -7,8 +7,9 @@
# 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.
# Notes: HA Core 2026.4 webhook support is optional; service-call wrappers remain compatible with polling or webhook transport.
###################################################################### ######################################################################
script: script:
@ -20,9 +21,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 +75,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 +92,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 }}"

@ -0,0 +1,99 @@
######################################################################
# @CCOSTAN - Follow Me on X
# For more info visit https://www.vcloudinfo.com/click-here
# Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# -------------------------------------------------------------------
# Tesla Model Y - Arrival and nightly plug-in reminders
# Garage speech + parents push reminders when the Tesla comes home low.
# -------------------------------------------------------------------
# Related Issue: 1279
# Notes: Expects Tesla Fleet UI entities `sensor.spaceship_battery_level`
# and `switch.spaceship_charge`.
# Notes: Uses `device_tracker.spaceship_location` for arrival/home state.
# Notes: The nightly parents reminder starts at 8 PM, waits until the
# car is home, then loops every 30 minutes via a custom event until it
# is charging or no longer home.
######################################################################
automation:
- alias: "Tesla Model Y - Garage Plug In Reminder"
id: 6a873341-c823-4d5d-9d42-fc2df52b93db
mode: restart
trigger:
- platform: state
entity_id: device_tracker.spaceship_location
to: 'home'
condition:
- condition: template
value_template: >-
{{ trigger.from_state is not none and
trigger.from_state.state not in ['home', 'unknown', 'unavailable'] }}
- condition: template
value_template: >-
{% set battery = states('sensor.spaceship_battery_level') %}
{{ battery not in ['unknown', 'unavailable', 'none', ''] and
(battery | float(100)) < 50 and
is_state('switch.spaceship_charge', 'off') }}
action:
- wait_for_trigger:
- platform: state
entity_id: cover.large_garage_door
from: 'closed'
to: 'open'
for: '00:03:20'
timeout: '00:10:00'
continue_on_timeout: false
- condition: template
value_template: >-
{% set battery = states('sensor.spaceship_battery_level') %}
{{ battery not in ['unknown', 'unavailable', 'none', ''] and
(battery | float(100)) < 50 and
is_state('switch.spaceship_charge', 'off') }}
- service: notify.alexa_media_garage
data:
message: >-
Reminder: the Tesla battery is at
{{ states('sensor.spaceship_battery_level') | float(0) | round(0) }} percent.
Please plug in the Model Y.
data:
type: announce
- service: script.send_to_logbook
data:
topic: "TESLA"
message: >-
Garage reminder announced to plug in the Model Y at
{{ states('sensor.spaceship_battery_level') | float(0) | round(0) }} percent.
- alias: "Tesla Model Y - Nightly Plug In Reminder"
id: 8b243b63-f5c3-4436-b596-0ec00a2108ab
mode: single
trigger:
- platform: time
at: '20:00:00'
- platform: event
event_type: event_tesla_model_y_nightly_loop
action:
- wait_template: "{{ is_state('device_tracker.spaceship_location', 'home') }}"
- condition: template
value_template: >-
{% set battery = states('sensor.spaceship_battery_level') %}
{{ is_state('device_tracker.spaceship_location', 'home') and
battery not in ['unknown', 'unavailable', 'none', ''] and
(battery | float(100)) < 50 and
is_state('switch.spaceship_charge', 'off') }}
- service: script.notify_engine
data:
title: "Tesla Plug In Reminder"
value1: >-
The Model Y is home, below 50 percent, and not plugged in.
Current battery:
{{ states('sensor.spaceship_battery_level') | float(0) | round(0) }} percent.
who: 'parents'
group: 'Tesla_Model_Y'
- service: script.send_to_logbook
data:
topic: "TESLA"
message: >-
Nightly plug-in reminder sent because the Model Y is home below 50 percent and not charging.
- delay: '00:30:00'
- event: event_tesla_model_y_nightly_loop

@ -10,8 +10,11 @@
# Notes: Expects JSON with title/message/type from the Tugtainer template. # Notes: Expects JSON with title/message/type from the Tugtainer template.
# Notes: Creates persistent notifications and stamps last-update time. # Notes: Creates persistent notifications and stamps last-update time.
# Notes: Fires `tugtainer_available_detected` when report contains `### Available:`. # Notes: Fires `tugtainer_available_detected` when report contains `### Available:`.
# Notes: Fires `tugtainer_home_assistant_core_updated` when `### Updated:` includes Home Assistant.
# Notes: Home Assistant changelog dispatch uses core-YYYY.M URL format from parsed/fallback version.
# Notes: Joanna dispatch cooldown uses mode=single with a 24-hour delay lockout. # Notes: Joanna dispatch cooldown uses mode=single with a 24-hour delay lockout.
# Notes: Blog post https://www.vcloudinfo.com/2026/02/tugtainer-docker-updates-home-assistant-notifications.html # - Blog: https://www.vcloudinfo.com/2026/04/joanna-home-assistant-changelog-digest-tugtainer.html
# Notes: Prior background post https://www.vcloudinfo.com/2026/02/tugtainer-docker-updates-home-assistant-notifications.html
###################################################################### ######################################################################
input_datetime: input_datetime:
@ -37,6 +40,43 @@ automation:
message: "{{ payload.message | default('Update event received') }}" message: "{{ payload.message | default('Update event received') }}"
event_type: "{{ payload.type | default('info') }}" event_type: "{{ payload.type | default('info') }}"
has_available_section: "{{ '### available:' in (message | lower) }}" has_available_section: "{{ '### available:' in (message | lower) }}"
updated_section: >-
{% set sections = message | regex_findall('(?is)###\\s*updated:\\s*(.*?)(?:\\n###\\s|$)') %}
{{ sections[0] if sections | count > 0 else '' }}
has_updated_section: "{{ updated_section | trim != '' }}"
ha_updated_line: >-
{% set matches = updated_section | regex_findall('(?im)^.*(?:home-assistant|ghcr\\.io/home-assistant/home-assistant).*$') %}
{{ matches[0] if matches | count > 0 else '' }}
ha_core_update_detected: "{{ has_updated_section and (ha_updated_line | trim != '') }}"
ha_version_from_updated_line: >-
{% set arrow_match = ha_updated_line | regex_findall('(?i)(?:->|→|to)\\s*v?([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{% if arrow_match | count > 0 %}
{{ arrow_match[-1] }}
{% else %}
{% set any_match = ha_updated_line | regex_findall('(?i)v?([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{{ any_match[-1] if any_match | count > 0 else '' }}
{% endif %}
ha_sensor_version: "{{ states('sensor.ha_installed_version') | string | trim }}"
ha_core_version_full: >-
{% set parsed = ha_version_from_updated_line | trim %}
{% if parsed != '' %}
{{ parsed }}
{% else %}
{% set sensor_matches = ha_sensor_version | regex_findall('([0-9]{4}\\.[0-9]+(?:\\.[0-9]+)?)') %}
{{ sensor_matches[0] if sensor_matches | count > 0 else '' }}
{% endif %}
ha_core_version_minor: >-
{% set minor = ha_core_version_full | trim | regex_findall('^([0-9]{4}\\.[0-9]+)') %}
{{ minor[0] if minor | count > 0 else '' }}
ha_core_changelog_url: >-
{% if ha_core_version_minor | trim != '' %}
https://www.home-assistant.io/changelogs/core-{{ ha_core_version_minor | trim }}
{% else %}
{% endif %}
ha_report_excerpt: >-
{% set excerpt = ha_updated_line | trim %}
{{ excerpt if excerpt != '' else (message | truncate(280, true)) }}
full_message: >- full_message: >-
{{ message }}{% if event_type %} ({{ event_type | upper }}){% endif %} {{ message }}{% if event_type %} ({{ event_type | upper }}){% endif %}
action: action:
@ -59,6 +99,19 @@ automation:
title: "{{ title }}" title: "{{ title }}"
event_type: "{{ event_type }}" event_type: "{{ event_type }}"
message: "{{ message }}" message: "{{ message }}"
- conditions:
- condition: template
value_template: "{{ ha_core_update_detected and (ha_core_version_minor | trim != '') }}"
sequence:
- event: tugtainer_home_assistant_core_updated
event_data:
title: "{{ title }}"
event_type: "{{ event_type }}"
message: "{{ message }}"
core_version_full: "{{ ha_core_version_full | trim }}"
core_version_minor: "{{ ha_core_version_minor | trim }}"
changelog_url: "{{ ha_core_changelog_url | trim }}"
report_excerpt: "{{ ha_report_excerpt | trim }}"
- alias: "Tugtainer - Dispatch Joanna For Available Updates" - alias: "Tugtainer - Dispatch Joanna For Available Updates"
id: tugtainer_dispatch_joanna_for_available_updates id: tugtainer_dispatch_joanna_for_available_updates
@ -93,3 +146,54 @@ automation:
Review the Tugtainer report and update all containers listed under the Review the Tugtainer report and update all containers listed under the
Available section. Report what was updated and any failures. Available section. Report what was updated and any failures.
- delay: "24:00:00" - delay: "24:00:00"
- alias: "Tugtainer - Dispatch Joanna For Home Assistant Core Digest"
id: tugtainer_dispatch_joanna_for_home_assistant_core_digest
description: "Dispatch Joanna after Home Assistant core is updated so changelog actions are captured in a GitHub digest issue."
mode: queued
trigger:
- platform: event
event_type: tugtainer_home_assistant_core_updated
variables:
report_title: "{{ trigger.event.data.title | default('Tugtainer update') }}"
report_message: "{{ trigger.event.data.message | default('Update event received') }}"
report_event_type: "{{ trigger.event.data.event_type | default('info') }}"
core_version_full: "{{ trigger.event.data.core_version_full | default('', true) | string | trim }}"
core_version_minor: "{{ trigger.event.data.core_version_minor | default('', true) | string | trim }}"
changelog_url: "{{ trigger.event.data.changelog_url | default('', true) | string | trim }}"
report_excerpt: "{{ trigger.event.data.report_excerpt | default('', true) | string | replace('\n', ' ') | replace('\r', ' ') | trim }}"
trigger_context: "HA automation tugtainer_dispatch_joanna_for_home_assistant_core_digest (Tugtainer - Dispatch Joanna For Home Assistant Core Digest)"
structured_request: |-
HA_CORE_UPDATE_DIGEST_REQUEST
core_version_full={{ core_version_full }}
core_version_minor={{ core_version_minor }}
changelog_url={{ changelog_url }}
report_title={{ report_title | string | replace('\n', ' ') | trim }}
report_event_type={{ report_event_type }}
report_excerpt={{ report_excerpt }}
condition:
- condition: template
value_template: "{{ core_version_minor != '' and changelog_url != '' }}"
action:
- service: script.send_to_logbook
data:
topic: "DOCKER"
message: >-
Home Assistant core update detected via Tugtainer ({{ core_version_full }}).
Joanna digest dispatch requested.
- service: script.joanna_dispatch
data:
trigger_context: "{{ trigger_context }}"
source: "home_assistant_automation.tugtainer_home_assistant_core_digest"
summary: >-
Home Assistant core updated to {{ core_version_full }} via Tugtainer.
Build changelog digest and open/refresh the GitHub Update Digest issue.
entity_ids:
- "input_datetime.tugtainer_last_update"
diagnostics: >-
report_title={{ report_title }},
report_event_type={{ report_event_type }},
core_version_full={{ core_version_full }},
core_version_minor={{ core_version_minor }},
changelog_url={{ changelog_url }}
request: "{{ structured_request }}"

@ -12,7 +12,7 @@
# - Treat 2+ minutes in a room as "being cleaned" and dequeue immediately (queue = remaining rooms). # - Treat 2+ minutes in a room as "being cleaned" and dequeue immediately (queue = remaining rooms).
# - Phase changes happen only after verified completion at dock (`task_status: completed`). # - Phase changes happen only after verified completion at dock (`task_status: completed`).
# - Guarded fallback: if docked with empty queue for 10 minutes but no `completed`, advance with `fallback_advance` log. # - Guarded fallback: if docked with empty queue for 10 minutes but no `completed`, advance with `fallback_advance` log.
# - Avoid reissuing `dreame_vacuum.vacuum_clean_segment` while already cleaning; only send a new segment job when starting/resuming or switching phases. # - Use `vacuum.clean_area` (HA 2026.3+) and keep room->area mappings aligned with Home Assistant Areas.
# - Jinja2 loop scoping: use a `namespace` when building lists (otherwise the queue can appear empty and get cleared). # - Jinja2 loop scoping: use a `namespace` when building lists (otherwise the queue can appear empty and get cleared).
# - If docked+completed still has queue entries, treat queue as stale and clear it before phase advance. # - If docked+completed still has queue entries, treat queue as stale and clear it before phase advance.
# - Mop phases use `sweeping_and_mopping` instead of mop-only. # - Mop phases use `sweeping_and_mopping` instead of mop-only.
@ -133,6 +133,30 @@ script:
{{ bath_ids }} {{ bath_ids }}
{% endif %} {% endif %}
segments_to_clean: "{{ queue_ints if queue_ints | length > 0 else phase_segments }}" segments_to_clean: "{{ queue_ints if queue_ints | length > 0 else phase_segments }}"
segment_area_name_map:
14: Kitchen
12: "Dining Room"
10: "Living Room"
7: "Master Bedroom"
15: Foyer
9: "Stacey Office"
13: Hallway
8: "Justin Bedroom"
6: "Paige Bedroom"
4: "Master Bathroom"
2: Office
1: "Pool Bath"
3: "Kids Bathroom"
cleaning_area_ids: >
{% set ns = namespace(ids=[]) %}
{% for seg in segments_to_clean %}
{% set area_name = segment_area_name_map.get(seg) %}
{% set aid = area_id(area_name) if area_name else none %}
{% if aid %}
{% set ns.ids = ns.ids + [aid] %}
{% endif %}
{% endfor %}
{{ ns.ids }}
# 0. Reseed the current phase when queue is empty. # 0. Reseed the current phase when queue is empty.
- choose: - choose:
@ -168,6 +192,19 @@ script:
- stop: 'No rooms left to clean today.' - stop: 'No rooms left to clean today.'
default: [] default: []
# 2b. Clean-area needs a mapped Home Assistant area ID for every segment
- choose:
- conditions:
- condition: template
value_template: "{{ cleaning_area_ids | length != segments_to_clean | length }}"
sequence:
- service: script.send_to_logbook
data:
topic: "VACUUM"
message: "Missing area mappings for one or more segments {{ segments_to_clean }}; skipping clean_area."
- stop: "Incomplete Home Assistant area mappings."
default: []
# 3. Start cleaning (but don't clobber an active job) # 3. Start cleaning (but don't clobber an active job)
- choose: - choose:
- conditions: - conditions:
@ -177,7 +214,7 @@ script:
- service: script.send_to_logbook - service: script.send_to_logbook
data: data:
topic: "VACUUM" topic: "VACUUM"
message: "Vacuum is already cleaning; queue/phase updated but not issuing a new segment job." message: "Vacuum is already cleaning; queue/phase updated but not issuing a new clean_area action."
- stop: "Already cleaning." - stop: "Already cleaning."
default: [] default: []
@ -192,12 +229,12 @@ script:
entity_id: vacuum.l10s_vacuum entity_id: vacuum.l10s_vacuum
data: data:
fan_speed: Standard fan_speed: Standard
- service: dreame_vacuum.vacuum_clean_segment - service: vacuum.clean_area
target: target:
entity_id: vacuum.l10s_vacuum entity_id: vacuum.l10s_vacuum
data: data:
# Clean the non-bathrooms if any, otherwise clean the bathrooms # Clean mapped Home Assistant areas for this phase queue.
segments: "{{ segments_to_clean }}" cleaning_area_id: "{{ cleaning_area_ids }}"
## 3. Automations ## 3. Automations
@ -294,22 +331,24 @@ automation:
id: kids_bathroom id: kids_bathroom
variables: variables:
room_map: room_map:
kitchen: {segment: 14, name: Kitchen} kitchen: {segment: 14, name: Kitchen, area: Kitchen}
dining_room: {segment: 12, name: 'Dining Room'} dining_room: {segment: 12, name: 'Dining Room', area: 'Dining Room'}
living_room: {segment: 10, name: 'Living Room'} living_room: {segment: 10, name: 'Living Room', area: 'Living Room'}
master_bedroom: {segment: 7, name: 'Master Bedroom'} master_bedroom: {segment: 7, name: 'Master Bedroom', area: 'Master Bedroom'}
foyer: {segment: 15, name: Foyer} foyer: {segment: 15, name: Foyer, area: Foyer}
stacey_office: {segment: 9, name: 'Stacey Office'} stacey_office: {segment: 9, name: 'Stacey Office', area: 'Stacey Office'}
formal_dining: {segment: 17, name: 'Formal Dining'} formal_dining: {segment: 17, name: 'Formal Dining', area: 'Formal Dining'}
hallway: {segment: 13, name: Hallway} hallway: {segment: 13, name: Hallway, area: Hallway}
justin_bedroom: {segment: 8, name: 'Justin Bedroom'} justin_bedroom: {segment: 8, name: 'Justin Bedroom', area: 'Justin Bedroom'}
paige_bedroom: {segment: 6, name: 'Paige Bedroom'} paige_bedroom: {segment: 6, name: 'Paige Bedroom', area: 'Paige Bedroom'}
master_bathroom: {segment: 4, name: 'Master Bathroom'} master_bathroom: {segment: 4, name: 'Master Bathroom', area: 'Master Bathroom'}
office: {segment: 2, name: Office} office: {segment: 2, name: Office, area: Office}
pool_bath: {segment: 1, name: 'Pool Bath'} pool_bath: {segment: 1, name: 'Pool Bath', area: 'Pool Bath'}
kids_bathroom: {segment: 3, name: 'Kids Bathroom'} kids_bathroom: {segment: 3, name: 'Kids Bathroom', area: 'Kids Bathroom'}
room_key: "{{ trigger.id }}" room_key: "{{ trigger.id }}"
room_name: "{{ room_map[room_key].name }}" room_name: "{{ room_map[room_key].name }}"
area_name: "{{ room_map[room_key].area }}"
area_id_value: "{{ area_id(area_name) if area_name else none }}"
segment_id: "{{ room_map[room_key].segment | int }}" segment_id: "{{ room_map[room_key].segment | int }}"
vac_state: "{{ states('vacuum.l10s_vacuum') }}" vac_state: "{{ states('vacuum.l10s_vacuum') }}"
on_demand: "{{ is_state('input_boolean.l10s_vacuum_on_demand', 'on') }}" on_demand: "{{ is_state('input_boolean.l10s_vacuum_on_demand', 'on') }}"
@ -319,7 +358,7 @@ automation:
- choose: - choose:
- conditions: - conditions:
- condition: template - condition: template
value_template: "{{ can_start }}" value_template: "{{ can_start and area_id_value is not none }}"
sequence: sequence:
- service: script.send_to_logbook - service: script.send_to_logbook
data: data:
@ -338,17 +377,17 @@ automation:
data: data:
fan_speed: Standard fan_speed: Standard
- continue_on_error: true - continue_on_error: true
service: dreame_vacuum.vacuum_clean_segment service: vacuum.clean_area
target: target:
entity_id: vacuum.l10s_vacuum entity_id: vacuum.l10s_vacuum
data: data:
segments: "{{ [segment_id] }}" cleaning_area_id: "{{ [area_id_value] }}"
- delay: "00:00:02" - delay: "00:00:02"
default: default:
- service: script.send_to_logbook - service: script.send_to_logbook
data: data:
topic: "VACUUM" topic: "VACUUM"
message: "One-off clean blocked: {{ room_name }} (vac={{ vac_state }}, on_demand={{ on_demand }}, queue='{{ queue_raw }}')." message: "One-off clean blocked: {{ room_name }} (area={{ area_name }}, area_id={{ area_id_value }}, vac={{ vac_state }}, on_demand={{ on_demand }}, queue='{{ queue_raw }}')."
- service: input_boolean.turn_off - service: input_boolean.turn_off
data: data:
entity_id: "{{ trigger.entity_id }}" entity_id: "{{ trigger.entity_id }}"

@ -8,6 +8,7 @@
# ------------------------------------------------------------------- # -------------------------------------------------------------------
# Notes: Keep this helper generic so package automations can reuse one schema. # Notes: Keep this helper generic so package automations can reuse one schema.
# Notes: Source defaults to home_assistant_automation.unknown when omitted. # Notes: Source defaults to home_assistant_automation.unknown when omitted.
# Notes: Automation dispatches are async_only by default so HA calls return quickly while BearClaw works in queue.
###################################################################### ######################################################################
joanna_dispatch: joanna_dispatch:
@ -64,3 +65,4 @@ joanna_dispatch:
user: "{{ normalized_user }}" user: "{{ normalized_user }}"
source: "{{ normalized_source }}" source: "{{ normalized_source }}"
context: "{{ normalized_context }}" context: "{{ normalized_context }}"
async_only: true

@ -1,15 +1,16 @@
#------------------------------------------- ######################################################################
# MQTT Sensor Configuration # @CCOSTAN - Follow Me on X
# Description: Various MQTT sensors for Home Assistant # For more info visit https://www.vcloudinfo.com/click-here
# # Original Repo : https://github.com/CCOSTAN/Home-AssistantConfig
# For more information and updates, visit: # -------------------------------------------------------------------
# https://www.vcloudinfo.com/click-here # MQTT Sensors - Medication reminder timestamp feed
# # MQTT-backed timestamp sensor used by medicine reminder workflows.
# Original Repository: https://github.com/CCOSTAN/Home-AssistantConfig # -------------------------------------------------------------------
#------------------------------------------- ######################################################################
mqtt: mqtt:
sensor: sensor:
- state_topic: "dash/medicine/medicine_time" - state_topic: "dash/medicine/medicine_time"
name: "Medicine time" name: "Medicine time"
unique_id: medicine_time_timestamp
device_class: "timestamp" device_class: "timestamp"

@ -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,16 +1,16 @@
<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.4.1">
<title>2026.3.2</title> <title>2026.4.1</title>
<linearGradient id="mJNFw" x2="0" y2="100%"> <linearGradient id="Vklnx" 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="dBGYj"><rect width="615" height="200" rx="30" fill="#FFF"/></mask> <mask id="NSwyq"><rect width="615" height="200" rx="30" fill="#FFF"/></mask>
<g mask="url(#dBGYj)"> <g mask="url(#NSwyq)">
<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(#mJNFw)"/> <rect width="615" height="200" fill="url(#Vklnx)"/>
</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.4.1</text>
<text x="55" y="138" textLength="500">2026.3.2</text> <text x="55" y="138" textLength="500">2026.4.1</text>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 811 B

Loading…
Cancel
Save

Powered by TurnKey Linux.